#include "vcl_stl.h"

#include <algorithm>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <poll.h>
#include <dirent.h>

#include "bkbase.h"
#include "bstcp.h"

#include "h_base.h"
#include "h_xml.h"
#include "h_conv.h"
#include "h_thread.h"
#include "h_lz77.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

#include "simbat_plugin.h"

#include <dlfcn.h>

#ifdef BK_AIX
   static HString SysName = "aix";
#endif
#ifdef BK_LINUX
#include <execinfo.h>
   static HString SysName = "linux";
#endif
#ifdef BK_HP
   static HString SysName = "hp";
#endif
#ifdef BK_SOLARIS
   static HString SysName = "solaris";
#endif

static const char *PASS_KEY = "sim bat 1.0.0   ";
static const int COMM_TIME_OUT = 30000;
static const char *PVER = "SIMJIE0104";

// === enum ===

enum HRestoreStatus
{
   rsNull,
   rsNeedRestore,
   rsRestoring,
   rsRestored
};

enum HAlarmTimeType
{
   attNone,
   attAtTheMoment,
   attDurationFromTheBatchStarting,
   attDurationFromTheJobStarting
};

enum HFuncCode
{
   fcAnnounceComplete,
   fcBackupInit,
   fcBackupFile,
   fcBreakpoint,
   fcCalcCriticalPath,
   fcChangeDate,
   fcChangeJobCondition,
   fcConfigure,
   fcDatabase,
   fcDownload,
   fcDump,
   fcExecuteCommand,
   fcFireEvent,
   fcGetSeq,
   fcGetVar,
   fcInquiry,
   fcInquiryCommand,
   fcKillProcess,
   fcLogin,
   fcMasterAscend,
   fcNextBatch,
   fcOutline,
   fcPauseBatch,
   fcPushAlarm,
   fcPushOutline,
   fcReloadConfig,
   fcRerunJob,
   fcRestartJob,
   fcRestoreJob,
   fcRestoreJobFail,
   fcRestoreJobSuccess,
   fcResumeBatch,
   fcSetParaJobNum,
   fcSetVar,
   fcSkipJob,
   fcSlaveSignon,
   fcStartBatch,
   fcStep,
   fcStopBatch,
   fcStopJob,
   fcStopAndSkipJob,
   fcTouchGateway,
   fcUploadSbt,
   fcViewAdditionalLog,
   fcViewLog,
   fcViewSummary,

   fcTouch,
   fcJobCancel,
   fcJobFail,
   fcJobRunning,
   fcJobSuccess,
   fcJobUnknown,
   fcBEGIN = fcAnnounceComplete,
   fcEND = fcJobUnknown
};

enum HPauseReason
{
   prUser,         // 操作员操作
   prFsCheck,      // 文件系统检查失败
   prRestart,      // 系统重启
   prHotback       // 热备失败
};

// === end of enum ===

// === struct ===

struct HLoginInfo
{
   HString Id;
   HString Password;
   set<HString> AuthSet;
};

struct HFsCheckInfo
{
   HString Path;
   int Size;
   HFsCheckInfo()
      : Size(100)
   {}
};

struct HClusterNodeInfo
{
   int Id;
   HString Addr;
   int Port;
   int GroupIdx;

   bool SignonFlag;             // 签到标识
   time_t LastTouchTime;        // 最近一次接触时间
   int RunningJobNum;           // 正在运行的进程数
   int RunningWaitJobNum;       // 正在运行的wait作业数
   int ParaJobNum;              // 最大并发作业数
   vector<int> RegionJobNumVec; // 每个区域运行进程数

   HClusterNodeInfo()
      : Id(-1), Port(-1), GroupIdx(0), SignonFlag(false),
        LastTouchTime(0), RunningJobNum(0), RunningWaitJobNum(0), ParaJobNum(0)
   {}
};

struct part_info
{
   int Idx;
   HString Id;
   HJobStatus Status;
   HRestoreStatus RestoreStatus;
   int ServerId;
   int RegionId;
   pid_t Pid;                    // job pid;
   int RunningTimes;             // Running times
   time_t LastUserStopTime;      // 最后一次用户停止时间
   pid_t LastTriggerPid;         // LastTriggerPid;
   time_t LastStatusChangeTime;  // 最后一次状态变化时间
   bool RestartFlag;             // 是否RESTART过
   bool PreparedFlag;            // 是否准备调度
   time_t BeginningTime;         // 作业第一次开始运行时间
   time_t StartTime;             // 作业本次开始运行时间
   time_t EndTime;               // 作业本次结束运行时间
   HXmlTree *Xml;                // Xml结点
   bool SlaveNotSignon;          // 作业UNKNOWN原因是否是SLAVE未按时签到

   part_info()
   {
      Idx = 0;
      Status = jsWaiting;
      RestoreStatus = rsNull;
      ServerId = -1;
      RegionId = 0;
      Pid = 0;
      RunningTimes = 0;
      LastUserStopTime = 0;
      LastTriggerPid = 0;
      LastStatusChangeTime = 0;
      RestartFlag = false;
      PreparedFlag = false;
      BeginningTime = 0;
      StartTime = 0;
      EndTime = 0;
      Xml = NULL;
      SlaveNotSignon = false;
   }
};

struct group_info;

struct job_info
{
   HString Name;
   HString GroupId;                    // group belongs to
   HJobType Type;                      // job, connector, extern_job,
                                       //    event_fire, event_wait, group
   HString Command;                    // if Type == Job, EventFire, EventWait
   HString RestoreCommand;             // if Type == Job
   HString AdditionalLog;              // if Type == Job
   HAlarmTimeType AlarmTimeType;       // if Type == Job
   HString AlarmTime;                  // if Type == Job
   HString ExternJob;                  // if Type == ExternJob
   vector<HString> ExternJobList;
   HString Group;                      // if Type == Group
   int ParaJobNum;                     // if Type == Group, Job
   bool WaitForOperator;               // if Type == EventWait
   bool Anchor;                        // if Type == Job, EventFire, EventWait
   HString Tag;                        // if Type == Job, EventFire, EventWait
   HString BusinessType;               // 业务类型

// if Type == Group, Job, EventFire, EventWait
   bool CondNot;
   HString ConditionFile;
   HString SpecialDate;
// end-if
// if Type == Job, EventFire, EventWait
   HString JobTrigger;
   HString PartitionFile;              // 分区表文件
   bool Exclusive;                     // 分区互斥
   int Priority;                       // 优先级
   int AutoRerun;                      // 自动重跑次数
   int AutoRerunTime;                  // 自动重跑间隔时间
// end-if

   int RunningGroupIdx;                // 运行结点组下标

// if Type == Job
   HString LoopStartTimeStr;
   HString LoopEndTimeStr;
   time_t LoopStartTime;
   time_t LoopEndTime;
   int LoopInterval;
   int LoopCount;
   HString LoopConditionVariable;
// end-if

   int PartitionStartTimeTick;         // 分区启动时间计数

   bool InScheduleList;
   HJobStatus Status;
   HRestoreStatus RestoreStatus;
   int ServerId;                       // Server id;
   int RegionId;                       // Region id;
   pid_t Pid;                          // job pid;
   int RunningTimes;                   // Running times
   time_t LastUserStopTime;            // 最后一次用户停止时间
   pid_t LastTriggerPid;               // LastTriggerPid;
   bool AlarmedFlag;                   // 已经告警

   vector<HString> SuccJobs;           // succeeding jobs
   vector<HString> Alias;              // job alias
   int InDegree;                       // in degree == 0 means can be scheduled
   int InDegreeWithinGroup;            // in degree within group
   HXmlTree *Xml;                      // xml tree node pointer
   group_info *GroupPtr;               // 所属组对象指针
   bool PartFlag;                      // Partition Flag, 第一次运行需要装载分区表
   vector<part_info> PartVec;
   int NumOfWaitingPart;
   int NumOfRunningPart;
   int NumOfCompletedPart;
   int NumOfFailedPart;
   int NumOfUnknownPart;
   int NumOfPreparedPart;
   time_t LastStatusChangeTime;        // 最后一次状态变化时间
   int IdleTimeSum;                    // 作业空闲时间累计（等待人工排错等）
   time_t IdleTimeBegin;               // 空闲开始时间
   bool RestartFlag;                   // 是否RESTART过
   HString RestartDate;                // RESTART作业日期
   HString RestartNextDate;            // RESTART作业下一日期
   bool PreparedFlag;                  // 是否准备调度
   bool GroupTrafficControlFlag;       // 组流控标识
   vector<HString> UpperGroupVec;      // 上级组，以及上上级组等
   int SchedulePartIdx;                // 上次调度分区下标
   time_t BeginningTime;               // 作业第一次开始运行时间
   time_t StartTime;                   // 作业本次开始运行时间
   time_t EndTime;                     // 作业本次结束运行时间
   bool SlaveNotSignon;                // 作业UNKNOWN原因是否是SLAVE未按时签到
   bool StopAndSkipFlag;               // 用户下了“停止并跳过”指令
   HString Section;                    // 属于哪个章节
   bool BreakpointFlag;                // 是否设置断点
   int Step;                           // Batch 作业步骤

   job_info()
   {
      Type = jtJob;
      AlarmTimeType = attNone;
      ParaJobNum = 0;
      WaitForOperator = false;
      Anchor = false;
      CondNot = false;
      Exclusive = false;
      Priority = 0;
      AutoRerun = 0;
      AutoRerunTime = 0;
      RunningGroupIdx = -1;
      LoopStartTime = 0;
      LoopEndTime = 0;
      LoopInterval = 0;
      LoopCount = 0;
      PartitionStartTimeTick = 0;
      InScheduleList = false;
      Status = jsWaiting;
      RestoreStatus = rsNull;
      ServerId = -1;
      RegionId = 0;
      Pid = 0;
      RunningTimes = 0;
      LastUserStopTime = 0;
      LastTriggerPid = 0;
      AlarmedFlag = false;
      InDegree = 0;
      InDegreeWithinGroup = 0;
      Xml = NULL;
      GroupPtr = NULL;
      PartFlag = false;
      NumOfWaitingPart = 0;
      NumOfRunningPart = 0;
      NumOfCompletedPart = 0;
      NumOfFailedPart = 0;
      NumOfUnknownPart = 0;
      NumOfPreparedPart = 0;
      LastStatusChangeTime = 0;
      IdleTimeSum = 0;
      IdleTimeBegin = 0;
      RestartFlag = false;
      PreparedFlag = false;
      GroupTrafficControlFlag = false;
      SchedulePartIdx = 0;
      BeginningTime = 0;
      StartTime = 0;
      EndTime = 0;
      SlaveNotSignon = false;
      StopAndSkipFlag = false;
      BreakpointFlag = false;
      Step = 0;
   }
};

struct group_info
{
   HString Name;
   int JobQty;
   int CompletedJobQty;
   int RunningJobQty;
   int FailedJobQty;         // not include extern_job
   int FailedExternJobQty;
   int UnknownJobQty;        // not include extern_job
   int UnknownExternJobQty;
   HString Agent;
   vector <HString> ExternJobVec;
   bool CondNot;
   HString ConditionFile;
   HString SpecialDate;
   int ConditionDateCache;   // 1 : not need test
                             // 2 : need but not test
                             // 3 : tested and exist
                             // 4 : tested and not exist
   int ConditionFileCache;   // 1 : not need test
                             // 2 : need but not test
                             // 3 : tested and exist
                             // 4 : tested and not exist
   int ParaJobNum;           // 最大并发进程数
   int RunningJobNum;        // 正在运行的进程数
   int RunningWaitJobNum;    // 正在运行的wait作业数
   int PreparedJobNum;       // 准备运行的非wait作业数

   job_info *AgentPtr;       // 代理作业指针

   group_info()
   {
      JobQty = 0;
      CompletedJobQty = 0;
      RunningJobQty = 0;
      FailedJobQty = 0;
      FailedExternJobQty = 0;
      UnknownJobQty = 0;
      UnknownExternJobQty = 0;
      CondNot = false;
      ConditionDateCache = 1;
      ConditionFileCache = 1;
      ParaJobNum = 0;
      RunningJobNum = 0;
      RunningWaitJobNum = 0;
      PreparedJobNum = 0;
      AgentPtr = NULL;
   }
};

struct HRunningJobInfo
{
   HString Name;                       // 作业名（含分区下标）
   int ServerId;                       // 运行服务器标识
   time_t JobStartTime;                // 作业启动时间
   pid_t Pid;                          // 作业进程号
   HJobStatus Status;                  // 作业状态

   HRunningJobInfo()
      : ServerId(0), JobStartTime(0), Pid(0),
        Status(jsRunning)
   {}
};

struct HAutoRerunInfo
{
   int AutoRerun;
   int AutoRerunTime;
   HAutoRerunInfo(int Arg1, int Arg2)
      : AutoRerun(Arg1), AutoRerunTime(Arg2)
   {}
};

struct HInputInfo
{
   HFuncCode Func;
   HString CodeSet;
   HString Id;
   HString ObjName;
   HString IpAddr;
   vector<HString> ArgVec;
   int Fd;
   time_t ArriveTime;
   int ThreadIdx;

   HInputInfo()
      : Func(fcTouch), Fd(-1), ArriveTime(0), ThreadIdx(0)
   {}
};

struct HStatusInputInfo
{
   HFuncCode Func;
   char FullName[200 + 1];
   job_info *JobPtr;
   bool PartFlag;
   int PartIdx;
   time_t JobStartTime;
   pid_t Pid;
   char IpAddr[80 + 1];
   bool SetConditionFlag;

   HStatusInputInfo()
      : JobPtr(NULL), PartFlag(false), PartIdx(0), JobStartTime(0), Pid(0), SetConditionFlag(false)
   {
      FullName[0] = '\0';
      IpAddr[0] = '\0';
   }
};

struct HDispatchInfo
{
   HExecInput Input;
   char Command[8192 + 1];
};

struct HPreparedJob
{
   HString FullName;
   bool PartFlag;
   int PartIdx;
   job_info *JobPtr;
   int DesServerId;
   int RegionId;

   HPreparedJob()
      : PartFlag(false), PartIdx(0), JobPtr(NULL), DesServerId(0), RegionId(0)
   {}
};

struct HSortJob
{
   HString JobName;
   int Priority;
   int PartitionStartTimeTick;
   int ScheduleListSeq;
   int Random;

   HSortJob()
      : Priority(0), PartitionStartTimeTick(0), ScheduleListSeq(0), Random(0)
   {}
};

struct HPostFuncInfo
{
   HFuncCode Func;
   HString FullName;
   int ServerId;
   time_t JobStartTime;

   HPostFuncInfo()
      : ServerId(0), JobStartTime(0)
   {}
};

struct HBackupObj
{
   HBackupObjType Type;
   char Name[500 + 1];
   long Offset;

   HBackupObj(HBackupObjType AType)
   {
      Type = AType;
      memset(Name, 0, sizeof(Name));
      Offset = 0;
   }

   HBackupObj(HBackupObjType AType, const char *AName)
   {
      Type = AType;
      memset(Name, 0, sizeof(Name));
      strncpy(Name, AName, sizeof(Name) - 1);
      Offset = 0;
   }

   HBackupObj(HBackupObjType AType, const char *AName, long AOffset)
   {
      Type = AType;
      memset(Name, 0, sizeof(Name));
      strncpy(Name, AName, sizeof(Name) - 1);
      Offset = AOffset;
   }
};

struct HBackupInfo_run
{
   HString Name;
   bool EnableFlag;
   bool PausedFlag;
   bool LogFlag;
   vector<HString> AddrVec;
   vector<HBackupObj> BackupObjVec;
   HMutex Mutex;
   int Idx;
   time_t LastSuccTime;
   time_t LastFailTime;
   HString Addr;
   int Port;
   int JobEventQty;

   HBackupInfo_run()
      : EnableFlag(false), PausedFlag(false), LogFlag(false),
        Idx(0), LastSuccTime(0), LastFailTime(0), Port(0), JobEventQty(0)
   {}
};

struct HAlarmObj
{
   HString System;
   HString Time;
   HString Seq;
   HString BatchDate;
   HString Type;
   HString Msg;
   HString Remark;
   HString Tag;
};

struct HHourMin
{
   int Hour;
   int Min;
   time_t Time;

   HHourMin()
      : Hour(0), Min(0), Time(0)
   {}
};

struct HBusinessTrafficControl
{
   HString Type;
   int ParaJobNum;
   int RunningJobNum;
   int PreparedJobNum;

   HBusinessTrafficControl()
      : ParaJobNum(0), RunningJobNum(0), PreparedJobNum(0)
   {}
};

// === end of struct ===

// === class ===

// 信号处理线程
class HSignalThread : public HThread
{
public :
   explicit HSignalThread(int ASigno)
      : FSigno(ASigno)
   {}
   virtual void Execute();

private :
   int FSigno;
};

// 检查父进程线程
class HCheckerThread : public HThread
{
public :
   virtual void Execute();
};

// 投递命令线程
class HPostFuncThread : public HThread
{
public :
   virtual void Execute();
};

// 分发作业线程
class HDispatcherThread : public HThread
{
public :
   virtual void Execute();
};

// 备份线程
class HBackupThread : public HThread
{
public :
   explicit HBackupThread(HBackupInfo_run &Backup)
      : FBackup(Backup)
   {}
   virtual void Execute();

private :
   HBackupInfo_run &FBackup;
};

// 告警线程
class HAlarmThread : public HThread
{
public :
   virtual void Execute();
};

// 摘要线程
class HOutlineThread : public HThread
{
public :
   virtual void Execute();
};

// 同步状态线程
class HSyncStatusThread : public HThread
{
public :
   HSyncStatusThread(int ASock)
      : HThread(), FSock(ASock), FRecCount(0)
   {}
   virtual void Execute();
private :
   void InitBatchStatus();
   void CheckBatchStatus();
   void CheckVarpool();
   void CheckCtldat();
   void CheckEvent();
   int FSock;
   HDbHeader FHeader;
   map<HString, HString> FVarMap;
   int FRecCount;
};

struct HHisInfo
{
   HString JobName;
   HString StartTime;
   HString EndTime;
   HString Status;
   int Elapse;

   HHisInfo()
      : Elapse(0)
   {}
};

// 写历史线程
class HWriteHisThread : public HThread
{
public :
   HWriteHisThread()
      : FIdx(0)
   {}
   virtual void Execute();
   void Init(const HString &ASystemName, const HString &ABatchSeq, const HString &ABatchType)
   {
      FMutex.Lock();
      FHisVec.clear();
      FIdx = 0;
      FSystemName = ASystemName;
      FBatchSeq = ABatchSeq;
      FBatchType = ABatchType;
      FMutex.Unlock();
   }
   void PushBack(const HHisInfo &Info)
   {
      FMutex.Lock();
      FHisVec.push_back(Info);
      FMutex.Unlock();
   }
private :
   vector<HHisInfo> FHisVec;
   int FIdx;
   HMutex FMutex;
   HString FSystemName;
   HString FBatchSeq;
   HString FBatchType;
};

// 生成作业顺序号
class HJobSeq
{
public :
   static void Init();
   static void GetJobSeq(char *JobSeq, int Qty = 1);
private :

   static int FSeq;
   static int FQty;
};

int HJobSeq::FSeq;
int HJobSeq::FQty;

// 记录作业日志
class HEventLogger
{
public :
   const static int ARRAY_SIZE = 100000;
   const static int SYNC_STEP = 400;
   const static int ALLOC_STEP = 2000;
   const static int SYNC_PERIOD = 30;
   static void Init(int FileFd, int FileUsed);
   static int GetFileFd();
   static int GetFileUsed();
   static bool Alloc();
   static bool Sync(int &SyncQty);
   static bool CheckSync(int &SyncQty);
   static bool Put(const HJobEvent &Event);
private :
   static int FFileFd;
   static int FFileUsed;
   static int FFileCapacity;
   static int FArrayHeader;
   static int FArrayUsed;
   static time_t FLastSyncTime;
   static HJobEvent FArray[ARRAY_SIZE];
   static HMutex FMutex;
};

int HEventLogger::FFileFd = -1;
int HEventLogger::FFileUsed = 0;
int HEventLogger::FFileCapacity = 0;
int HEventLogger::FArrayHeader = 0;
int HEventLogger::FArrayUsed = 0;
time_t HEventLogger::FLastSyncTime = 0;
HJobEvent HEventLogger::FArray[HEventLogger::ARRAY_SIZE];
HMutex HEventLogger::FMutex;

// 计算关键路径
class HCritical
{
public :

   HCritical()
      : PartQty(1), Time(0), MaxTime(0), SuccJobIdx(0), CompletedFlag(false)
   {}

   static void WriteCriticalPath(FILE *f);

private :

   HString Name;
   HString Tag;
   int PartQty;
   HString StartTime;
   HString EndTime;
   int Time;
   set<HString> SuccJobs;
   vector<HString> SuccJobVec;

   int MaxTime;
   HString MaxNode;
   int SuccJobIdx;
   bool CompletedFlag;

   static map<HString, HCritical> CriticalGraph;

   static void InitGraph();
   static void AddLink(const HString &From, const HString &To);
   static void CalcPath();
   static void PrintPath(FILE *f);
};

map<HString, HCritical> HCritical::CriticalGraph;

// 计算每个作业所属章节
class HCalcSection : public HCheckBase
{
public :
   HCalcSection(const char *ASimbatBase, const HString &AJobNameDelimiter, set<HString> &ASectionSet)
      : HCheckBase(ASimbatBase, AJobNameDelimiter), SectionSet(ASectionSet)
   {}
   void Calc();
private :
   set<HString> SectionSet;
};

// 协同进程
class HSystem
{
public :
   HSystem()
   {}
   bool Init();
   void Close();
   int Run(const char *Command);
   void RunAnnounce();
private :
   bool WriteRequest(char FuncCode, const char *ArgStr);
   void Announce();
   int FFdArr[2];
};

// 通讯线程
class HListenerThread : public HThread
{
public :
   HListenerThread(int AIdx)
      : FIdx(AIdx)
   {}
   virtual void Execute();
private :
   int FIdx;
};

// 等待队列，一Master对多Slave模式。Master不阻塞。
template<typename T>class HWaitingQueue
{
public :
    void Init(int QueueLength);
    int GetLength()
    {
       return FQueueLength;
    }
    void Close();

// Slave 作为 Writer 时用
    bool TimedPushBack(const T &Info, int Millisecond);
    bool TimedPushBackArray(const T Info[], int &Qty, int Millisecond);
// Slave 作为 Reader 时用
    bool TimedPop(T &Info, int Millisecond);

// Master 作为 Reader 时用
    void GetCurrForRead(int &Header, int &Qty);
    void Remove(int Qty);

// Master 作为 Writer 时用
    void GetCurrForWrite(int &Header, int &Qty);
    void Add(int Qty);
    bool MasterPushBackNoWait(const T &Info);

// Master 用
    const T &GetByIdx(int Idx) const;
    T &GetByIdx(int Idx);
private :
    T *FQueue;
    int FQueueLength;
    int FHeader;
    int FQty;
    HMutex FMutex;
    HThreadCond *FCond;
};

// === end of class ===

// === function prototype ===

static HString CalcNextScheduleDate(const HString &Date);
static int GatewayPushAlarm(const char *InfoStr, char ErrStr[200 + 1]);
static int GatewayPushOutline(const char *InfoStrArray[], int Qty, char ErrStr[200 + 1]);

static bool CreateEmptyFile(const HString &FileName, char ErrMsg[2048 + 1]);
static bool CreateEmptyXmlFile(const HString &FileName, char ErrMsg[2048 + 1]);
static HString PartIdxString(int PartIdx);
static bool SplitPartId(const HString &FullName, HString &JobName, int &PartIdx);
static HAlarmTimeType StringToAlarmTimeType(const HString &String);
static HString GetEnv(const HString &VarName);
static HString ToShellArg(const HString &Str);
static const char *GetFuncCodeString(HFuncCode FuncCode);
static bool ConvFile(HCodeSet From, HCodeSet To,
   const HString &FromFile, const HString &ToFile);
static bool WriteFileToSocket(int Sock, const char *FileName);
static bool ConvAndCompressFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, bool CompressFlag);
static bool CompressFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, bool Large, int ThreadIdx);
static bool ReadFileFromSocket(int Sock, const char *FileName);
static bool IsSafeTime(time_t CurrTime, time_t Time);
static bool IsSpecialDate(const HString &SpecialDate);
static const char *BatchStatusToString(HBatchStatus Status);

static bool ParseDailyBatchString(const HString &Src,
   HString &BatchName, vector<HHourMin> &TimeVec);
static bool ParseBatchAlarmString(const HString &Src,
   HString &BatchName, int &Hour, int &Min, int &EndHour, int &EndMin);
static void WriteCtldat();
static void AddVarpoolVersion();
static void AddConfigVersion();
static void ExecCmd(int Idx, HExecInput &Input, HString Cmd, HExecOutput &Output);
static void DoTrigger(const HString &FullName, const char *Type1,
   const char *Type2 = NULL, const char *Type3 = NULL);
static void DoBatchTrigger(const char *Type, bool Sleep = false);
static void DoBatchAlarmTrigger(const char *Type);
static void ChangeBatchStatus(HBatchStatus Status);
static void SetBatchStopStatus();
static void CheckBatchStop();
static void LoadFromCfg(HXmlTree &Cfg, bool InitFlag = false);
static void LoadFromCfgCust(HXmlTree &Cfg);
static void LoadFromCfgAuth(HXmlTree &Cfg);
static bool LoadClusterInfo(HXmlTree &Cfg);
static void LoadVarpool();
static bool SetEnv(const char *Name, const char *Value, bool HotbackFlag = true);
static int AdjustPriority(const HString &JobName);
static int AdjustRunningGroupIdx(const HString &JobName);
static HString AdjustBusinessType(const HString &JobName);
static HAutoRerunInfo AdjustAutoRerun(const HString &JobName);
static void BackupSummary(const char *OldBatchTime, const char *NewBatchTime);
static bool AscendAnnounce(const HString &Addr, int Port);
static void GroupAddJobNum(group_info *GroupPtr, bool IsWaitJob, time_t StatusChangeTime);
static void GroupSubJobNum(group_info *GroupPtr, bool IsWaitJob, time_t StatusChangeTime);
static void WriteSummary(const job_info &Job, HJobStatus Status, int PartIdx);
static void ReturnSuccess(const HInputInfo &Input, const char *Result = NULL);
static void ReturnFail(const HInputInfo &Input, const char *ErrStr = NULL);
static bool CheckWhiteList(const HInputInfo &Input, int SystemNameIdx = 3);
static void SaveOutlineToXml(HXmlTree *Xml);
static void SaveNewStatusToXml(HXmlTree *Xml, time_t LastRefreshTime);
static bool ConvFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, int ThreadIdx);
static char CheckPassword(const HString &Id, const HString &Password,
   const HString &Auth);
static bool RerunJob(job_info &Job, int PartIdx);
static bool RestartJob(const HString &JobName, const HString &Date, const HString &NextDate);
static void RestoreJob(job_info &Job, int PartIdx);
static bool SkipJob(job_info &Job, int PartIdx);
static void KillOneJob(const HString FullName, pid_t Pid, int ServerId, time_t JobStartTime);
static void KillJob(const HString &FullName);
static bool StopJob(job_info &Job, int PartIdx);
static void SlaveSignon(bool &NeedRetry);
static bool SlaveGetConfig(const char *Addr, int Port);
static bool SlaveGetVarpool(const char *Addr, int Port);
static bool DownloadAuthFile(const char *Addr, int Port);
static void TouchGateway();
static bool operator<(const HSortJob &lhs, const HSortJob &rhs);
static bool operator<(const HHourMin &lhs, const HHourMin &rhs);
static void SortTimeVec(vector<HHourMin> &TimeVec);

static void RunVec(vector<HPreparedJob> &JobVec);
static void Schedule();
static void FlushInput();
static void FlushStatus();
static void SlaveFlushStatus();
static int GetInput(HInputInfo &Input);
static void ReloadStatus(job_info &Job, int PartIdx, HJobStatus Status, int ServerId, int RegionId, pid_t Pid, time_t StatusChangeTime);
static void ChangeStatus(const HString &FullName, HJobStatus Status, int ServerId, int RegionId,
   pid_t Pid, const char *CurrTime = NULL);
static bool Load(const HString &BatName, bool Resume);
static bool StartBatch(const char *BatName, HString &ErrStr, bool Resume = false, const char *StartDate = NULL, const char *NextDate = NULL);
static void InitOrResumeBatch();
static bool Init();
static void DoIt();
static void PushBackupObj(const HBackupObj &Obj);
static void PushAlarmObj(const HAlarmObj &Obj);
static void PushAlarmObj(const HString &Type, const HString &Msg);
static void PushAlarmObj(const HString &Type, const HString &Msg, const job_info &Job);
static void InitBackupVec();
static bool DoBackupInit(const HString &Addr, int Port, const char *BatchTime, int &JobEventQty);
static bool DoBackupBatchStatus(const HString &Addr, int Port);
static bool DoBackupJobEvents(const HString &Addr, int Port, int &JobEventQty);
static bool DoBackupCtldat(const HString &Addr, int Port);
static bool DoBackupVarpool(const HString &Addr, int Port);
static bool DoBackupConfig(const HString &Addr, int Port);
static bool DoBackupCurrentSbt(const HString &Addr, int Port);
static bool DoBackupEvent(const HString &Addr, int Port, const char *ObjName);
static bool DoBackupAuth(const HString &Addr, int Port);
static bool DoBackupShr(const HString &Addr, int Port, const char *ObjName);
static bool DoBackupJobLog(const HString &Addr, int Port, const char *ObjName, unsigned long Offset);
static bool DoBackupOther(const HString &Addr, int Port, const char *ObjName);
static void DoHotback();
static void DoDump();

static void FuncAnnounceComplete(const HInputInfo &Info);
static void FuncBackupInit(const HInputInfo &Info);
static void FuncBackupFile_aux(const HInputInfo &Info);
static void FuncBackupFile(const HInputInfo &Info);
static void FuncBreakpoint(const HInputInfo &Info);
static void FuncCalcCriticalPath(const HInputInfo &Info);
static void FuncChangeDate(const HInputInfo &Info);
static void FuncChangeJobCondition(const HInputInfo &Info);
static void FuncConfigure(const HInputInfo &Info);
static void FuncDatabase(const HInputInfo &Info);
static void FuncDownload(const HInputInfo &Info);
static void FuncDump(const HInputInfo &Info);
static void FuncExecuteCommand(const HInputInfo &Info);
static void FuncFireEvent(const HInputInfo &Info);
static void FuncGetSeq(const HInputInfo &Info);
static void FuncGetVar(const HInputInfo &Info);
static void FuncStep(const HInputInfo &Info);
static void FuncInquiry(const HInputInfo &Info);
static void FuncInquiryCommand(const HInputInfo &Info);
static void FuncKillProcess(const HInputInfo &Info);
static void FuncLogin(const HInputInfo &Info);
static void FuncMasterAscend(const HInputInfo &Info);
static void FuncNextBatch(const HInputInfo &Info);
static void FuncOutline(const HInputInfo &Info);
static void FuncPauseBatch(const HInputInfo &Info);
static void FuncPushAlarm_aux(const HInputInfo &Info);
static void FuncPushAlarm(const HInputInfo &Info);
static void FuncPushOutline(const HInputInfo &Info);
static void FuncSetVar(const HInputInfo &Info);
static void FuncReloadConfig(const HInputInfo &Info);
static void FuncRerunJob(const HInputInfo &Info);
static void FuncRestartJob(const HInputInfo &Info);
static void FuncRestoreJob(const HInputInfo &Info);
static void FuncRestoreJobFail(const HInputInfo &Info);
static void FuncRestoreJobSuccess(const HInputInfo &Info);
static void FuncResumeBatch(const HInputInfo &Info);
static void FuncSetParaJobNum(const HInputInfo &Info);
static void FuncSkipJob(const HInputInfo &Info);
static void FuncSlaveSignon(const HInputInfo &Info);
static void FuncStartBatch(const HInputInfo &Info);
static void FuncStopBatch(const HInputInfo &Info);
static void FuncStopJob(const HInputInfo &Info);
static void FuncStopAndSkipJob(const HInputInfo &Info);
static void FuncTouchGateway_aux(const HInputInfo &Info);
static void FuncTouchGateway(const HInputInfo &Info);
static void FuncUploadSbt(const HInputInfo &Info);
static void FuncViewAdditionalLog(const HInputInfo &Info);
static void FuncViewLog(const HInputInfo &Info);
static void FuncViewSummary(const HInputInfo &Info);

static int DbConnect(const char *System, char ErrStr[200 + 1]);
static int DbCountStatus(const char *System, int *Result, char ErrStr[200 + 1]);
static int DbClearStatus(const char *System, char ErrStr[200 + 1]);
static int DbWriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1]);
static int DbReadStatus(const char *System, int Idx, char *RecType,
   char Record[2000 + 1], char ErrStr[200 + 1]);
static int DbRewriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1]);

/*
   ValueList format :
   StartTime | EndTime | Elapse | JobStatus
 */
static int DbWriteHis(const char *System, const char *BatchSeq, const char *JobName,
   const char *ValueList, char ErrStr[200 + 1]);
static int DbReadStatistic(const char *System, const char *BatchType, const char *JobName,
   int *n, int Elapse[10], char ErrStr[200 + 1]);
static int DbWriteStatistic(const char *System, const char *BatchType, const char *JobName,
   int n, int Elapse[10], char ErrStr[200 + 1]);
static int GatewayDbConnect(const char *System, char ErrStr[200 + 1]);
static int GatewayDbCountStatus(const char *System, int *Result, char ErrStr[200 + 1]);
static int GatewayDbClearStatus(const char *System, char ErrStr[200 + 1]);
static int GatewayDbWriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1]);
static int GatewayDbReadStatus(const char *System, int Idx, char *RecType,
   char Record[2000 + 1], char ErrStr[200 + 1]);
static int GatewayDbRewriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1]);
static int GatewayDbWriteHis(const char *System, const char *BatchSeq, const char *JobName,
   const char *ValueList, char ErrStr[200 + 1]);
static int GatewayDbReadStatistic(const char *System, const char *BatchType, const char *JobName,
   int *n, int Elapse[10], char ErrStr[200 + 1]);
static int GatewayDbWriteStatistic(const char *System, const char *BatchType, const char *JobName,
   int n, int Elapse[10], char ErrStr[200 + 1]);

static void LoadBatchProperty(HXmlTree *Xml, job_info &JobInfo);

// === end of function prototype ===

// === global variable ===

static int SockSch = -1;                            // 调度器主端口文件描述符，由simbat_ctl传递过来
static const char *SimbatBase = NULL;               // SIM-BAT根目录
static pid_t ParentPid = -1;                        // 父进程pid
static bool ExitFlag = false;                       // 退出标识
static time_t ServerStartTime = 0;                  // 本服务器启动时间
static time_t LastActionTime = 0;                   // 最后一次人工操作时间
static struct timeval LastScheduleTime;             // 最后一次调度时间
static vector<HString> SocketExecNameVec;           // 与simbat_exec通讯路径
static HString ConfigFile;                          // config.xml路径
static HString ConfigCustFile;                      // config_cust.xml路径
static HString ConfigLockFile;                      // 配置锁文件路径
static time_t AlarmBatchStartTime = 0;              // 告警批量启动时间
static time_t NextBatchAlarmTime = 0;               // 告警批量最晚启动时间
static time_t NextBatchEndAlarmTime = 0;            // 告警批量最晚结束时间
static bool BatchAlarmedFlag = false;               // 批量未按时启动告警是否已产生
static bool BatchEndAlarmedFlag = false;            // 批量未按时启动告警是否已产生
static int SockDb = -1;                             // 数据库进程通讯管道文件描述符
static HString VarpoolFile;                         // var_pool文件名
static HString VarpoolLockFile;                     // var_pool锁文件名
static HString CtldatFile;                          // 控制信息文件名
static HString CtldatLockFile;                      // 控制信息锁文件名
static char LastBatchTime[8 + 6 + 1];               // 最后一次批量启动时间
static bool RebuildSummary = false;                 // 重建summary.txt标识
static int JobEventFd = -1;                         // job_event 文件描述符
static HString BatchDate;                           // 批量业务日期
static HString NextBatchDate;                       // 批量下一业务日期
static HString NextScheduleDate;                    // 计划批量下一业务日期
static HString EndBatchDate;                        // 循环批量结束日期
static int BatchTimeTick = 0;                       // 从批量启动开始时间计数
static int RunningJobNum = 0;                       // 正在运行的进程数
static int TotalJobNum = 0;                         // 作业总数（多分区作业算一个）
static int CompletedJobNum = 0;                     // 完成作业总数（多分区作业算一个）
static bool AnnounceCompleteFlag = true;            // 主机登基声明完成标识
static time_t SlaveSignonTime = 0;                  // 最后一次从机签到时间
static time_t MasterCheckTime = 0;                  // 最后一次主机检查从机状态时间
static time_t FsCheckTime = 0;                      // 最后一次文件系统检查时间
static vector<HPreparedJob> PreparedJobVec;         // 准备发起的作业（该数组是RunVec函数接口专用，定义成全局变量是为了空间不要反复折腾）
static FILE *ParaMonFile = NULL;                    // 并发数监控文件
static HString CurrentSection;                      // 当前章节
static bool ChangeJobConditionFlag = false;         // 作业条件被修改标识
static bool BatchRestartFlag = false;               // 批量完成后又有作业RESTART过
static time_t LastHotbackSuccTime = 0;              // 上次热备成功时间
static time_t LastHotbackFailTime = 0;              // 上次热备失败时间
static vector<HBackupObj> HotbackObjVec;            // 待热备对象
static int HotbackJobEventQty = 0;                  // 已经同步的job_event数量
static char GatewayIdx = 0;                         // 当前连接的网关
static time_t TouchGatewayTime = 0;                 // 最后连接网关时间
static int AuthVersion = 0;
static SIMBAT_PLUGIN_PUSH_ALARM AlarmFunc = NULL;
static bool HasAlarmFunc = false;
static HMutex AlarmMutex;
static int AlarmIdx = 0;
static vector<HAlarmObj> AlarmVec;
static SIMBAT_PLUGIN_PUSH_OUTLINE OutlineFunc = NULL;
static bool HasOutlineFunc = false;
static HMutex OutlineMutex;
static int OutlineIdx = 0;
static vector<HString> OutlineVec;
static map<HString, HString> OutlineMap;
static bool HasDbFunc = false;
static bool DbDisableFlag = false;
static SimbatPluginDbInitArg DbFunc;
static HWriteHisThread *WriteHisThread = NULL;
static int LastActionPoint;
static int LastActionPoint_main;
static int LastActionPoint_Init;
static int LastActionPoint_DoIt;
static int LastActionPoint_Schedule;
static int LastActionPoint_RunVec;
static int LastActionPoint_ChangeStatus;
static int LastActionPoint_StartBatch;
static int LastActionPoint_Load;

static const int LastActionBase_main = 0x00010000;
static const int LastActionBase_Init = 0x00020000;
static const int LastActionBase_DoIt = 0x00030000;
static const int LastActionBase_Schedule = 0x00040000;
static const int LastActionBase_RunVec = 0x00050000;
static const int LastActionBase_ChangeStatus = 0x00060000;
static const int LastActionBase_StartBatch = 0x00070000;
static const int LastActionBase_Load = 0x00080000;

#define SET_ACTION_POINT(func, pos) LastActionPoint_##func = LastActionPoint = LastActionBase_##func + pos;

// 从sbt文件读入
static bool AutoSkip = false;                       // 作业出错自动跳过标识
static bool AcrossDayEnd = false;                   // 批量跨日
static int AutoRerun = 0;                           // 作业自动重跑次数
static int AutoRerunTime = 30;                      // 作业自动重跑间隔时间
static HString FadingPeriod;                        // 批量褪色周期
static int PartitionDitherCount = 0;                // 分区抖动次数
static int PartitionDitherStep = 0;                 // 分区抖动步长（毫秒）
static HString Banner;                              // 批量横幅
static HString BatchTrigger;                        // 批量触发器
static HString JobTrigger;                          // 作业触发器
static HString MainGroupName;                       // 主组名
static set<HString> SectionSet;                     // 批量章节作业列表

// 从 config.xml 读入
static bool AppendLogToSummary = false;             // 是否把出错作业log附加到summary.txt最后
static bool EnableAutoSkip = true;                  // 是否允许自动跳过功能
static HLogLocation LogLocation = llLocal;          // log文件位置
static int ParaJobNum = 4;                          // 最大并发进程数
static int NumOfRegion = 1;                         // 区域数
static HCodeSet CodeSet = csUtf8;                   // 字符集
static HString SystemName;                          // 本系统名称
static HString AlarmSystemName;                     // 本系统告警名称
static HString SystemDescription;                   // 本系统描述
static bool DedicatedFlag = false;                  // 主调度器专注于调度任务，不执行除了锚定之外的作业
static int LocalPort = 0;                           // 调度器主端口
static HString SyslogDir_config;                    // 系统日志目录
static HString SyslogDir;                           // 系统日志目录
static bool ClearEventFlag = true;                  // 批量启动时清空events目录标识
static bool ClearPartFlag = true;                   // 批量启动时清空part目录标识
static HString JobNameDelimiter = ":";              // 作业名分隔符，缺省是冒号
static HString ShrDir_config;                       // shr目录路径
static HString ShrDir;                              // shr目录路径
static HString LogDir_config;                       // 作业log路径
static HString LogDir;                              // 作业log路径
static HString DailyBatch;                          // 每日例行批量时间和名称
static HString BatchAlarm;                          // 批量未按时启动和停止告警
static HString DailyBatchName;                      // 每日例行批量名称
static vector<HHourMin> DailyBatchTimeVec;          // 每日例行批量时间数组
static time_t NextDailyBatchTime = 0;               // 下一次每日例行批量时间
static HString AlarmBatchName;                      // 需要告警批量名称
static int SlaveSignonCheckTime = 0;                // Slave多长时间没有signon认为是失联
static bool ClusterFlag = false;                    // 是否集群
static int MyId = -1;                               // 本服务器ID（从0开始）
static HNodeRole MyRole = nrMaster;                 // 本服务器角色（MASTER/SLAVE/BACKUP）
static bool HotbackEnableFlag = false;              // 是否启动热备
static bool HotbackPausedFlag = false;              // 是否暂停热备
static HString HotbackAddr;                         // 热备机IP地址
static int HotbackPort = -1;                        // 热备机端口
static bool MasterDownFlag = false;                 // 主机宕机，热备机接管

static int NumOfListener = 5;                       // LISTENER线程数量，建议值：总并发数 / 20
static int NumOfDispatcher = 5;                     // DISPATCHER线程数量，建议值：总并发数 / 20
static int NumOfExecutor = 3;                       // simbat_exec进程数量，建议值：单机并发数 / 10
static int StatusInputQueueSize = 20480;            // 作业状态输入队列大小，建议值：总并发数 * 2
static int DispatchQueueSize = 20480;               // 作业分发队列大小，建议值：总并发数
                                                    // 另外还有一个BACKLOG参数，参见simbat_ctl.cpp内注释

static int MasterTimeDiff = 0;                      // 与主调度器时差，主调度器时间减去本机器时间
static int MasterTimeDiffError = -1;                // 与主调度器时差误差
static HPauseReason PauseReason = prRestart;        // 批量暂停原因
static HString NextBatchName;                       // 下一个预定批量名称
static map<HString, HBackupInfo_run> BackupMap;         // 若干备份集群
static vector<HString> GatewayAddrVec;              // 网关地址（可以一个或者两个）
static bool DownloadAuth = false;                   // 从网关下载权限文件
static set<HString> ScheduleDateSet;                // 批量排期
static map<HString, set<HString> > WhiteListMap;    // GATEWAY 白名单

// 复杂数据结构
static HMutex GraphMutex;                           // 整个调度图数据结构锁
static HSystem System;                              // 执行system调用的协同进程
static HSyslog Syslog;                              // 系统日志对象
static HXmlTree SbtXml;                             // 当前批量XML

static HBatch Batch;                                // 当前批量状态
static HCtldat Ctldat;                              // 控制信息
static vector<HClusterNodeInfo> ClusterNodeVec;     // 集群配置信息
static map<HString, int> RunningGroupMap;           // 运行组名称映射表
static map<HString, HLoginInfo> LoginMap;           // 权限信息
static vector<HFsCheckInfo> FsCheckVec;             // 需要检查的文件系统信息
static map<HString, job_info> Graph;                // 整个调度图
static map<HString, group_info> GroupMap;           // 所有组信息
static list<HString> ScheduleList;                  // 待调度作业队列
static map<HString, HRunningJobInfo> RunningJobMap; // 正在运行作业信息(slave)
static map<HString, time_t> JobAlarmMap;            // 待告警作业
static HWaitingQueue<HInputInfo> InputQueue;        // 输入队列
static HWaitingQueue<HStatusInputInfo>
       StatusInputQueue;                            // 作业状态输入队列
static HWaitingQueue<HPostFuncInfo>
       PostFuncQueue;                               // 投递命令队列
static HWaitingQueue<HDispatchInfo>
       DispatchQueue;                               // 作业分发队列
static map<HString, HBusinessTrafficControl>
       BusinessTrafficControlMap;                   // 业务流控信息

typedef void (*HFuncPtr)(const HInputInfo &Input);

static HFuncPtr FuncArray[] =
{
   FuncAnnounceComplete,
   FuncBackupInit,
   FuncBackupFile,
   FuncBreakpoint,
   FuncCalcCriticalPath,
   FuncChangeDate,
   FuncChangeJobCondition,
   FuncConfigure,
   FuncDatabase,
   FuncDownload,
   FuncDump,
   FuncExecuteCommand,
   FuncFireEvent,
   FuncGetSeq,
   FuncGetVar,
   FuncInquiry,
   FuncInquiryCommand,
   FuncKillProcess,
   FuncLogin,
   FuncMasterAscend,
   FuncNextBatch,
   FuncOutline,
   FuncPauseBatch,
   FuncPushAlarm,
   FuncPushOutline,
   FuncReloadConfig,
   FuncRerunJob,
   FuncRestartJob,
   FuncRestoreJob,
   FuncRestoreJobFail,
   FuncRestoreJobSuccess,
   FuncResumeBatch,
   FuncSetParaJobNum,
   FuncSetVar,
   FuncSkipJob,
   FuncSlaveSignon,
   FuncStartBatch,
   FuncStep,
   FuncStopBatch,
   FuncStopJob,
   FuncStopAndSkipJob,
   FuncTouchGateway,
   FuncUploadSbt,
   FuncViewAdditionalLog,
   FuncViewLog,
   FuncViewSummary,
};

// === end of global variable ===

#ifdef SIMBAT_ENTERPRISE
#include "../../simbat_enterprise/h_enterprise_s.cpp"
#endif

// === class method ===

void HSignalThread::Execute()
{
   int TmpSigno;
   sigset_t SigSet;

   sigemptyset(&SigSet);
   sigaddset(&SigSet, FSigno);
   sigwait(&SigSet, &TmpSigno);
   ExitFlag = true;
}

void HCheckerThread::Execute()
{
   for ( ; !ExitFlag; )
   {
      if ( kill(ParentPid, 0) == -1 )
      {
         BkPrintError("[WARNING]Line %d, parent down", __LINE__);
         ExitFlag = true;
         break;
      }
      sleep(10);
   }
}

void HPostFuncThread::Execute()
{
   char ErrMsg[2048 + 1];

   for ( ; !ExitFlag; )
   {
      HPostFuncInfo Info;
      if ( !PostFuncQueue.TimedPop(Info, 10000) )
      {
         continue;
      }
      if ( Info.Func == fcKillProcess )
      {
         bool UnknownFlag = false;
         bool SuccFlag = false;
         HString Result;
         char AddrStr[200 + 1];
         char TmpStr[500 + 1];
         HClusterNodeInfo &Node = ClusterNodeVec[Info.ServerId];
         snprintf(AddrStr, 200, "%s:%d", Node.Addr.c_str(), Node.Port);
         snprintf(TmpStr, 500, "T|||%s|%ld|%d", Info.FullName.c_str(), Info.JobStartTime, MyId);
         snprintf(ErrMsg, 2048, "CallServer(%s, KILL_PROCESS), Job = %s, JobStartTime = %s",
            AddrStr, Info.FullName.c_str(), TimeToString(Info.JobStartTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "KILL_PROCESS", Info.FullName,
            TmpStr, true, Result, true, &UnknownFlag, &SuccFlag);
         if ( r && !UnknownFlag && !SuccFlag )
         {
            snprintf(AddrStr, 200, "%s:%d", "127.0.0.1", LocalPort);
            snprintf(TmpStr, 500, "h|||%s|%ld", Info.FullName.c_str(), Info.JobStartTime);
            snprintf(ErrMsg, 2048, "CallServer(%s, JOB_UNKNOWN), Job = %s",
               AddrStr, Info.FullName.c_str());
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_UNKNOWN", Info.FullName, TmpStr, true, Result);
         }
      }
      else if ( Info.Func == fcInquiryCommand )
      {
         HString Result;
         char AddrStr[200 + 1];
         char TmpStr[500 + 1];
         HClusterNodeInfo &Node = ClusterNodeVec[Info.ServerId];
         snprintf(AddrStr, 200, "%s:%d", Node.Addr.c_str(), Node.Port);
         snprintf(TmpStr, 500, "V|||%s|%ld|%d", Info.FullName.c_str(), Info.JobStartTime, MyId);
         snprintf(ErrMsg, 2048, "CallServer(%s, INQUIRY_COMMAND), ServerId = %d, Job = %s, JobStartTime = %s",
            AddrStr, Info.ServerId, Info.FullName.c_str(), TimeToString(Info.JobStartTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         bool SuccFlag = false;
         bool UnknownFlag = false;
         bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "INQUIRY_COMMAND", Info.FullName,
            TmpStr, false, Result, false, &UnknownFlag, &SuccFlag);
         if ( r && !UnknownFlag )
         {
            snprintf(AddrStr, 200, "%s:%d", "127.0.0.1", LocalPort);
            if ( SuccFlag )
            {
               const char *s = Result.c_str();
               switch ( *s )
               {
               case 'R' :
                  snprintf(ErrMsg, 2048, "CallServer(%s, JOB_RUNNING), Job = %s",
                     AddrStr, Info.FullName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                  snprintf(TmpStr, 500, "a|||%s|%ld|%s", Info.FullName.c_str(), Info.JobStartTime, s + 1);
                  CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_RUNNING", Info.FullName, TmpStr, true, Result);
                  break;
               case 'S' :
                  snprintf(ErrMsg, 2048, "CallServer(%s, JOB_SUCCESS), Job = %s",
                     AddrStr, Info.FullName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                  snprintf(TmpStr, 500, "c|||%s|%ld|%s", Info.FullName.c_str(), Info.JobStartTime, s + 1);
                  CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_SUCCESS", Info.FullName, TmpStr, true, Result);
                  break;
               case 'F' :
                  snprintf(ErrMsg, 2048, "CallServer(%s, JOB_FAIL), Job = %s",
                     AddrStr, Info.FullName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                  snprintf(TmpStr, 500, "d|||%s|%ld|%s", Info.FullName.c_str(), Info.JobStartTime, s + 1);
                  CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_FAIL", Info.FullName, TmpStr, true, Result);
                  break;
               }
            }
            else
            {
               if ( Result == "not_found" )
               {
                  snprintf(ErrMsg, 2048, "CallServer(%s, JOB_CANCEL), Job = %s",
                     AddrStr, Info.FullName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                  snprintf(TmpStr, 500, "b|||%s|%ld", Info.FullName.c_str(), Info.JobStartTime);
                  CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_CANCEL", Info.FullName, TmpStr, true, Result);
               }
            }
         }
      }
      else if ( Info.Func == fcPauseBatch )
      {
         HString Result;
         char AddrStr[200 + 1];
         char TmpStr[500 + 1];
         snprintf(AddrStr, 200, "%s:%d", "127.0.0.1", LocalPort);
         snprintf(TmpStr, 500, "2|||FS_CHECK");
         snprintf(ErrMsg, 2048, "CallServer(%s, PAUSE_BATCH)", AddrStr);
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         CallServer(Syslog, COMM_TIME_OUT, AddrStr, "PAUSE_BATCH", "FS_CHECK", TmpStr, true, Result);
      }
      else if ( Info.Func == fcJobCancel )
      {
         int i;
         HString Result;
         char AddrStr[200 + 1];
         char TmpStr[500 + 1];
         HClusterNodeInfo &Node = ClusterNodeVec[Info.ServerId];
         snprintf(AddrStr, 200, "%s:%d", Node.Addr.c_str(), Node.Port);
         snprintf(TmpStr, 500, "b|||%s|%ld", Info.FullName.c_str(), Info.JobStartTime);
         for ( i = 0; i < 60 && !ExitFlag; i++ )
         {
            snprintf(ErrMsg, 2048, "CallServer(%s, JOB_CANCEL), Job = %s",
               AddrStr, Info.FullName.c_str());
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "JOB_CANCEL", Info.FullName,
               TmpStr, true, Result, true);
            if ( !r || Result != "0" )
            {
               sleep(10);
               continue;
            }
            break;
         }
         if ( i == 60 )
         {
            snprintf(ErrMsg, 2048, "Send JOB_CANCEL(%s), retry %d times fail", Info.FullName.c_str(), i);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
   }
}

void HDispatcherThread::Execute()
{
   char ErrMsg[2048 + 1];

   for ( ; !ExitFlag; )
   {
      HDispatchInfo Info;
      if ( !DispatchQueue.TimedPop(Info, 10000) )
      {
         continue;
      }
      Info.Input.CommStartTime = time(NULL);
      Info.Input.CmdStrLen = strlen(Info.Command);
      HString RequestStr;
      char ReqStr[16384 + 500 + 1];
      snprintf(ReqStr, 500, "S|||%s|%d|%d|%ld|%ld|%s|%s|%ld|%ld|%d|%d|%s|", Info.Input.FullName,
         Info.Input.SrcServerId, Info.Input.DesServerId, Info.Input.JobStartTime, Info.Input.CommStartTime,
         Info.Input.RerunFlag ? "Y" : "N", Info.Input.RestartFlag ? "Y" : "N",
         Info.Input.LoopStartTime, Info.Input.LoopEndTime, Info.Input.LoopInterval, Info.Input.LoopCount, Info.Input.JobSeq);
      char *CmdStr = ReqStr + strlen(ReqStr);
      BkHexToAsc(Info.Command, CmdStr, Info.Input.CmdStrLen);
      CmdStr[Info.Input.CmdStrLen * 2] = '\0';
      HString Result;
      char AddrStr[200 + 1];
      HClusterNodeInfo &Node = ClusterNodeVec[Info.Input.DesServerId];
      snprintf(AddrStr, 200, "%s:%d", Node.Addr.c_str(), Node.Port);
      snprintf(ErrMsg, 2048, "CallServer(%s, EXECUTE_COMMAND), Job = %s, Cmd = %s",
         AddrStr, Info.Input.FullName, Info.Command);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      bool UnknownFlag = false;
      time_t StartTime = time(NULL);
      bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "EXECUTE_COMMAND", Info.Input.FullName,
         ReqStr, true, Result, false, &UnknownFlag);
      int DeltaTime = time(NULL) - StartTime;
      if ( DeltaTime > 3 )
      {
         snprintf(ErrMsg, 2048, "CallServer(%s, EXECUTE_COMMAND), Job = %s, elapsed = %d",
            AddrStr, Info.Input.FullName, DeltaTime);
         Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
      }
      if ( !r || Result != "0" )
      {
// 发 job_cancel or job_unknown
         int i;
         char TmpStr[500 + 1];
         HClusterNodeInfo &Node = ClusterNodeVec[Info.Input.SrcServerId];
         snprintf(AddrStr, 200, "%s:%d", "127.0.0.1", Node.Port);
         snprintf(TmpStr, 500, "%s|||%s|%ld", UnknownFlag ? "h" : "b", Info.Input.FullName, Info.Input.JobStartTime);
         for ( i = 0; i < 60; i++ )
         {
            if ( ExitFlag )
            {
               break;
            }
            if ( i == 0 && UnknownFlag )
            {
               sleep(10);
            }
            snprintf(ErrMsg, 2048, "CallServer(%s, %s), Job = %s, DesServerId = %d",
               AddrStr, UnknownFlag ? "JOB_UNKNOWN" : "JOB_CANCEL", Info.Input.FullName, Info.Input.DesServerId);
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, UnknownFlag ? "JOB_UNKNOWN" : "JOB_CANCEL", Info.Input.FullName,
               TmpStr, true, Result, true);
            if ( !r )
            {
               sleep(10);
               continue;
            }
            break;
         }
         if ( i == 60 )
         {
            snprintf(ErrMsg, 2048, "Send %s(%s), retry %d times fail",
               UnknownFlag ? "JOB_UNKNOWN" : "JOB_CANCEL", Info.Input.FullName, i);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
   }
}

void HBackupThread::Execute()
{
   char ErrMsg[2048 + 1];

   for ( ; !ExitFlag; BsSleep(1000) )
   {
      if ( !FBackup.EnableFlag || FBackup.PausedFlag )
      {
         continue;
      }
      time_t CurrTime = time(NULL);
      if ( CurrTime - FBackup.LastFailTime < 60 )
      {
         continue;
      }
      for ( ; ; )
      {
         FBackup.Mutex.Lock();
         if ( FBackup.Idx == FBackup.BackupObjVec.size() )
         {
            if ( FBackup.BackupObjVec.size() > 200 )
            {
               FBackup.BackupObjVec.clear();
               FBackup.Idx = 0;
            }
            FBackup.Mutex.Unlock();
            break;
         }
         HBackupObj Obj = FBackup.BackupObjVec[FBackup.Idx++];
         FBackup.Mutex.Unlock();
         if ( Obj.Type == boJobLog && !FBackup.LogFlag )
         {
            continue;
         }
         bool SuccFlag = false;
         switch ( Obj.Type )
         {
         case boInit :
            SuccFlag = DoBackupInit(FBackup.Addr, FBackup.Port, Obj.Name, FBackup.JobEventQty);
            break;
         case boBatchStatus :
            SuccFlag = DoBackupBatchStatus(FBackup.Addr, FBackup.Port);
            break;
         case boJobEvents :
            SuccFlag = DoBackupJobEvents(FBackup.Addr, FBackup.Port, FBackup.JobEventQty);
            break;
         case boCtldat :
            SuccFlag = DoBackupCtldat(FBackup.Addr, FBackup.Port);
            break;
         case boVarpool :
            SuccFlag = DoBackupVarpool(FBackup.Addr, FBackup.Port);
            break;
         case boConfig :
            SuccFlag = DoBackupConfig(FBackup.Addr, FBackup.Port);
            break;
         case boCurrentSbt :
            SuccFlag = DoBackupCurrentSbt(FBackup.Addr, FBackup.Port);
            break;
         case boEvent :
            SuccFlag = DoBackupEvent(FBackup.Addr, FBackup.Port, Obj.Name);
            break;
         case boAuth :
            SuccFlag = DoBackupAuth(FBackup.Addr, FBackup.Port);
            break;
         case boShr :
            SuccFlag = DoBackupShr(FBackup.Addr, FBackup.Port, Obj.Name);
            break;
         case boJobLog :
            SuccFlag = DoBackupJobLog(FBackup.Addr, FBackup.Port, Obj.Name, Obj.Offset);
            break;
         default :
            SuccFlag = DoBackupOther(FBackup.Addr, FBackup.Port, Obj.Name);
         }
         CurrTime = time(NULL);
         if ( SuccFlag )
         {
            FBackup.LastSuccTime = CurrTime;
            continue;
         }
         FBackup.LastFailTime = CurrTime;
         if ( FBackup.LastSuccTime > 0 && CurrTime - FBackup.LastSuccTime > 600 )
         {
            snprintf(ErrMsg, 2048, "Backup(%s), LastSuccTime = %s, backup paused",
               FBackup.Name.c_str(), TimeToString(FBackup.LastSuccTime).c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            FBackup.PausedFlag = true;
            {
               HXmlTree Cfg;
               HFileLockWrite Lock(ConfigLockFile.c_str());
               if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
               {
                  snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
                  Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               }
               else
               {
                  vector<HXmlTree*> NodeVec;
                  Cfg.GetChildren("BACKUP", NodeVec);
                  for ( int i = 0; i < NodeVec.size(); i++ )
                  {
                     HXmlTree *Node = NodeVec[i];
                     if ( Node->GetValueUpper("NAME") == FBackup.Name )
                     {
                        Node->SetValue("PAUSED", "true");
                        break;
                     }
                  }
                  Cfg.SaveToFile(ConfigFile.c_str());
               }
            }
         }
         break;
      }
   }
}

void HAlarmThread::Execute()
{
   char ErrMsg[2048 + 1];

   for ( ; !ExitFlag; BsSleep(1000) )
   {
      for ( ; ; )
      {
         AlarmMutex.Lock();
         if ( AlarmIdx == AlarmVec.size() )
         {
            if ( AlarmVec.size() > 200 )
            {
               AlarmVec.clear();
               AlarmIdx = 0;
            }
            AlarmMutex.Unlock();
            break;
         }
         HAlarmObj Obj = AlarmVec[AlarmIdx++];
         AlarmMutex.Unlock();
         if ( !HasAlarmFunc )
         {
            continue;
         }
         char ErrStr[200 + 1];
         char InfoStr[8192 + 1];
         snprintf(InfoStr, 8192, "%s|%s|%s|%s|%s|%s|%s|%s", Obj.System.c_str(), Obj.Time.c_str(), Obj.Seq.c_str(),
            Obj.BatchDate.c_str(), Obj.Type.c_str(), Obj.Msg.c_str(), Obj.Remark.c_str(), Obj.Tag.c_str());
         if ( AlarmFunc(InfoStr, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "PushAlarm(%s, %s, %s, %s, %s, %s, %s, %s) fail, errstr = %s",
               Obj.System.c_str(), Obj.Time.c_str(), Obj.Seq.c_str(),
               Obj.BatchDate.c_str(), Obj.Type.c_str(), Obj.Msg.c_str(), Obj.Remark.c_str(), Obj.Tag.c_str(), ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
   }
}

void HOutlineThread::Execute()
{
   char ErrMsg[2048 + 1];
   time_t LastPushTime = 0;

   for ( ; !ExitFlag; BsSleep(1000) )
   {
      char ErrStr[200 + 1];
      if ( MyRole == nrMaster )
      {
         if ( !HasOutlineFunc )
         {
            continue;
         }
         time_t CurrTime = time(NULL);
         if ( CurrTime - LastPushTime > 10 )
         {
            LastPushTime = CurrTime;
         }
         else
         {
            continue;
         }
         GraphMutex.Lock();
         int TotalJobQty = 0;
         int WaitingJobQty = 0;
         int RunningJobQty = 0;
         int SkippedJobQty = 0;
         int FailedJobQty = 0;
         int SuccessJobQty = 0;
         int SectionTotalJobQty = 0;
         int SectionWaitingJobQty = 0;
         int SectionRunningJobQty = 0;
         int SectionSkippedJobQty = 0;
         int SectionFailedJobQty = 0;
         int SectionSuccessJobQty = 0;
         for ( map<HString, job_info>::iterator Iter = Graph.begin();
               Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventWait &&
                 Job.Type != jtTime && Job.Type != jtBatch )
            {
               continue;
            }
            TotalJobQty++;
            switch ( Job.Status )
            {
            case jsWaiting :
               WaitingJobQty++;
               break;
            case jsRunning :
               RunningJobQty++;
               break;
            case jsSkipped :
               SkippedJobQty++;
               break;
            case jsFail :
            case jsUnknown :
               FailedJobQty++;
               break;
            case jsSuccess :
               SuccessJobQty++;
               break;
            }
            if ( Job.Section == CurrentSection )
            {
               SectionTotalJobQty++;
               switch ( Job.Status )
               {
               case jsWaiting :
                  SectionWaitingJobQty++;
                  break;
               case jsRunning :
                  SectionRunningJobQty++;
                  break;
               case jsSkipped :
                  SectionSkippedJobQty++;
                  break;
               case jsFail :
               case jsUnknown :
                  SectionFailedJobQty++;
                  break;
               case jsSuccess :
                  SectionSuccessJobQty++;
                  break;
               }
            }
         }
         char InfoStr[500 + 1];
         snprintf(InfoStr, 500, "%s|%s|%s|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d",
            SystemName.c_str(), SystemDescription.c_str(), BatchDate.c_str(),
            Batch.StartTime, Batch.EndTime, CurrentSection.c_str(), Batch.Status,
            TotalJobQty, SuccessJobQty, RunningJobQty,
            FailedJobQty, SkippedJobQty, WaitingJobQty,
            SectionTotalJobQty, SectionSuccessJobQty, SectionRunningJobQty,
            SectionFailedJobQty, SectionSkippedJobQty, SectionWaitingJobQty);
         GraphMutex.Unlock();
         const char *StrPtr = InfoStr;
         if ( OutlineFunc(&StrPtr, 1, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "PushOutline() fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
         continue;
      }
      for ( ; ; )
      {
         OutlineMutex.Lock();
         if ( OutlineIdx == OutlineVec.size() )
         {
            if ( OutlineVec.size() > 200 )
            {
               OutlineVec.clear();
               OutlineIdx = 0;
            }
            OutlineMutex.Unlock();
            break;
         }
         const HString InfoStr = OutlineVec[OutlineIdx++];
         OutlineMutex.Unlock();
         vector<HString> StrVec;
         SplitString(InfoStr.c_str(), '|', StrVec, true);
         OutlineMap[StrVec[0]] = InfoStr;
         if ( !HasOutlineFunc )
         {
            continue;
         }
         time_t CurrTime = time(NULL);
         if ( CurrTime - LastPushTime > 10 )
         {
            LastPushTime = CurrTime;
            int n = OutlineMap.size();
            if ( n == 0 )
            {
               continue;
            }
            const char **OutlineArray = (const char**)malloc(sizeof(char*) * n);
            if ( OutlineArray == NULL )
            {
               snprintf(ErrMsg, 2048, "malloc(%d) fail", sizeof(char*) * n);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               continue;
            }
            int i = 0;
            for ( map<HString, HString>::iterator Iter = OutlineMap.begin();
                  Iter != OutlineMap.end(); Iter++ )
            {
               OutlineArray[i++] = Iter->second.c_str();
            }
            if ( OutlineFunc(OutlineArray, n, ErrStr) != 0 )
            {
               snprintf(ErrMsg, 2048, "PushOutline() fail, errstr = %s", ErrStr);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            free(OutlineArray);
         }
      }
   }
}

void HSyncStatusThread::InitBatchStatus()
{
   char ErrMsg[2048 + 1];

   if ( !HasDbFunc )
   {
      return;
   }

   snprintf(ErrMsg, 8192, "InitBatchStatus begin");
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   FHeader = HDbHeader();
   FVarMap.clear();

   int RecLen = 0;
   char HexStr[2048 + 1];
   char ErrStr[200 + 1];

   if ( DbFunc.Connect(SystemName.c_str(), ErrStr) != 0 )
   {
      snprintf(ErrMsg, 8192, "Connect fail, errstr = %s", ErrStr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      DbDisableFlag = true;
      return;
   }

   if ( DbFunc.CountStatus(SystemName.c_str(), &FRecCount, ErrStr) != 0 )
   {
      snprintf(ErrMsg, 8192, "Count fail, errstr = %s", ErrStr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      DbDisableFlag = true;
      return;
   }

   if ( FRecCount > 0 )
   {
      char RecType[200 + 1];
      for ( int i = 0; i < FRecCount; i++ )
      {
         char Buf[1000 + 1];
         if ( DbFunc.ReadStatus(SystemName.c_str(), i, RecType, HexStr, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "ReadStatus(%d) fail, errstr = %s", i, ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            DbDisableFlag = true;
            return;
         }
         BkRightTrim(RecType);
         BkRightTrim(HexStr);
         if ( strcmp(RecType, "HEADER") == 0 )
         {
            RecLen = strlen(HexStr) / 2;
            BkAscToHex(&FHeader, HexStr, RecLen);
         }
         if ( strcmp(RecType, "VAR") == 0 )
         {
            RecLen = strlen(HexStr) / 2;
            BkAscToHex(Buf, HexStr, RecLen);
            Buf[RecLen] = '\0';
            const char *s = strchr(Buf, '=');
            HString Name = HString(Buf, s - Buf);
            HString Value = HString(s + 1);
            FVarMap[Name] = Value;
         }
      }
   }
   else
   {
      RecLen = sizeof(HDbHeader);
      BkHexToAsc((char*)&FHeader, HexStr, RecLen);
      HexStr[RecLen * 2] = '\0';
      if ( DbFunc.WriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
      {
         snprintf(ErrMsg, 8192, "WriteStatus(0) fail, errstr = %s", ErrStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         DbDisableFlag = true;
         return;
      }
      FRecCount = 1;
   }

   snprintf(ErrMsg, 8192, "InitBatchStatus end, BatchStartTime = %s, RecCount = %d, EventRecCount = %d",
      FHeader.Batch.StartTime, FRecCount, FHeader.EventRecCount);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   CheckBatchStatus();
}

void HSyncStatusThread::CheckBatchStatus()
{
   char ErrMsg[2048 + 1];
   char ErrStr[200 + 1];

   if ( !HasDbFunc )
   {
      return;
   }

   snprintf(ErrMsg, 2048, "CheckBatchStatus begin");
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);

   if ( DbFunc.Connect(SystemName.c_str(), ErrStr) != 0 )
   {
      snprintf(ErrMsg, 2048, "Connect fail, errstr = %s", ErrStr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      DbDisableFlag = true;
      return;
   }

   DbDisableFlag = false;

   int RecLen = 0;
   char HexStr[2048 + 1];
   HBatch Batch;

   {
      HFileLockRead Lock("$SIMBAT_BASE/tmp/batch_status.lck");
      char FileName[500 + 1];
      snprintf(FileName, sizeof(FileName), "%s/status/batch_status", ShrDir.c_str());
      FILE *f = fopen(FileName, "rb");
      if ( f != NULL )
      {
         fread(&Batch, sizeof(HBatch), 1, f);
         fclose(f);
      }
   }

   if ( Batch.Status != bsNotStarted )
   {
      char ErrStr[200 + 1];
      if ( strcmp(Batch.StartTime, FHeader.Batch.StartTime) != 0 )
      {
         if ( DbFunc.ClearStatus(SystemName.c_str(), ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "ClearStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            DbDisableFlag = true;
            return;
         }
         FHeader = HDbHeader();
         FHeader.Batch = Batch;
         FVarMap.clear();
         RecLen = sizeof(HDbHeader);
         BkHexToAsc((char*)&FHeader, HexStr, RecLen);
         HexStr[RecLen * 2] = '\0';
         if ( DbFunc.WriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "WriteStatus(0) fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            DbDisableFlag = true;
            return;
         }
         FRecCount = 1;
         char FileName[500 + 1];
         snprintf(FileName, sizeof(FileName), "%s/status/current.sbt", ShrDir.c_str());
         FILE *f = fopen(FileName, "rb");
         if ( f != NULL )
         {
            char Buf[1000];
            int r;
            for ( ; ; )
            {
               r = fread(Buf, 1, 1000, f);
               if ( r <= 0 )
               {
                  break;
               }
               RecLen = r;
               BkHexToAsc(Buf, HexStr, RecLen);
               HexStr[RecLen * 2] = '\0';
               if ( DbFunc.WriteStatus(SystemName.c_str(), FRecCount, "SBT", HexStr, ErrStr) != 0 )
               {
                  snprintf(ErrMsg, 2048, "WriteStatus(%d) fail, errstr = %s", FRecCount, ErrStr);
                  Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
                  DbDisableFlag = true;
                  return;
               }
               FRecCount++;
               FHeader.SbtRecCount++;
            }
            fclose(f);
            RecLen = sizeof(HDbHeader);
            BkHexToAsc((char*)&FHeader, HexStr, RecLen);
            HexStr[RecLen * 2] = '\0';
            if ( DbFunc.RewriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
            {
               snprintf(ErrMsg, 2048, "RewriteStatus(0) fail, errstr = %s", ErrStr);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               DbDisableFlag = true;
               return;
            }
         }
         CheckVarpool();
         CheckCtldat();
      }
      else if ( memcmp(&Batch, &FHeader.Batch, sizeof(HBatch)) != 0 )
      {
         memcpy(&FHeader.Batch, &Batch, sizeof(HBatch));
         RecLen = sizeof(HDbHeader);
         BkHexToAsc((char*)&FHeader, HexStr, RecLen);
         HexStr[RecLen * 2] = '\0';
         if ( DbFunc.RewriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "RewriteStatus(0) fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            DbDisableFlag = true;
            return;
         }
      }
   }

   snprintf(ErrMsg, 2048, "CheckBatchStatus end, BatchStartTime = %s, RecCount = %d",
      FHeader.Batch.StartTime, FRecCount);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   CheckEvent();
}

void HSyncStatusThread::CheckVarpool()
{
   char ErrMsg[2048 + 1];

   if ( !HasDbFunc || DbDisableFlag )
   {
      return;
   }

   int RecLen = 0;
   char HexStr[2048 + 1];
   char FileName[500 + 1];
   HString VarpoolFile;
   HString VarpoolLockFile;

   snprintf(FileName, sizeof(FileName), "%s/status/var_pool", ShrDir.c_str());
   VarpoolFile = FileName;
   snprintf(FileName, sizeof(FileName), "%s/tmp/var_pool.lck", SimbatBase);
   VarpoolLockFile = FileName;

   HXmlTree Xml;
   vector<HXmlTree*> Nodes;
   bool Changed = false;
   char ErrStr[200 + 1];

   if ( FileExist(VarpoolFile) )
   {
      HFileLockRead Lock(VarpoolLockFile.c_str());
      if( !Xml.LoadFromFile(VarpoolFile.c_str(), false) )
      {
         snprintf(ErrMsg, 2048, "Load var_pool fail, %s", Xml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
   }

   Xml.GetChildren(NULL, Nodes);

   for ( int i = 0; i < Nodes.size(); i++ )
   {
      HString Name = Nodes[i]->GetTag();
      HString Value = Nodes[i]->GetData();
      map<HString, HString>::iterator Iter = FVarMap.find(Name);
      if ( Iter == FVarMap.end() || Iter->second != Value )
      {
         char Buf[500 + 1];
         FVarMap[Name] = Value;
         snprintf(Buf, sizeof(Buf), "%s=%s", Name.c_str(), Value.c_str());
         RecLen = strlen(Buf);
         BkHexToAsc(Buf, HexStr, RecLen);
         HexStr[RecLen * 2] = '\0';
         if ( DbFunc.WriteStatus(SystemName.c_str(), FRecCount, "VAR", HexStr, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "WriteStatus(%d) fail, errstr = %s", FRecCount, ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            DbDisableFlag = true;
            return;
         }
         FRecCount++;
         FHeader.VarRecCount++;
         Changed = true;
      }
   }

   if ( Changed )
   {
      RecLen = sizeof(HDbHeader);
      BkHexToAsc((char*)&FHeader, HexStr, RecLen);
      HexStr[RecLen * 2] = '\0';
      if ( DbFunc.RewriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
      {
         snprintf(ErrMsg, 2048, "RewriteStatus(0) fail, errstr = %s", ErrStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         DbDisableFlag = true;
         return;
      }
   }

   snprintf(ErrMsg, 2048, "CheckVarpool end, RecCount = %d", FRecCount);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
}

void HSyncStatusThread::CheckCtldat()
{
   char ErrMsg[2048 + 1];

   if ( !HasDbFunc || DbDisableFlag )
   {
      return;
   }

   int RecLen = 0;
   char HexStr[2048 + 1];
   char FileName[500 + 1];
   HString CtldatFile;
   HString CtldatLockFile;

   snprintf(FileName, sizeof(FileName), "%s/status/control.dat", ShrDir.c_str());
   CtldatFile = FileName;
   snprintf(FileName, sizeof(FileName), "%s/tmp/ctldat.lck", SimbatBase);
   CtldatLockFile = FileName;

   char ErrStr[200 + 1];
   HCtldat Ctldat;

   {
      HFileLockRead Lock(CtldatLockFile.c_str());
      FILE *f = fopen(CtldatFile.c_str(), "rb");
      if ( f == NULL )
      {
         snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", CtldatFile.c_str(), errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      fread(&Ctldat, sizeof(HCtldat), 1, f);
      fclose(f);
   }

   if ( memcmp(&FHeader.Ctldat, &Ctldat, sizeof(HCtldat)) != 0 )
   {
      memcpy(&FHeader.Ctldat, &Ctldat, sizeof(HCtldat));
      RecLen = sizeof(HDbHeader);
      BkHexToAsc((char*)&FHeader, HexStr, RecLen);
      HexStr[RecLen * 2] = '\0';
      if ( DbFunc.RewriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
      {
         snprintf(ErrMsg, 2048, "RewriteStatus(0) fail, errstr = %s", ErrStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         DbDisableFlag = true;
         return;
      }
   }

   snprintf(ErrMsg, 2048, "CheckCtldat end");
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
}

void HSyncStatusThread::CheckEvent()
{
   char ErrMsg[2048 + 1];

   if ( !HasDbFunc || DbDisableFlag )
   {
      return;
   }

   snprintf(ErrMsg, 2048, "CheckEvent begin, EventRecCount = %d", FHeader.EventRecCount);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);

   int RecLen = 0;
   char HexStr[2048 + 1];
   char FileName[500 + 1];
   struct stat Stat;

   snprintf(FileName, sizeof(FileName), "%s/status/job_events", ShrDir.c_str());
   int r = stat(FileName, &Stat);

   if ( r == -1 )
   {
      return;
   }

   int RecCount2 = Stat.st_size / sizeof(HJobEvent);

   snprintf(ErrMsg, 2048, "RecCount2 = %d", RecCount2);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);

   if ( FHeader.Batch.Status == bsNotStarted || RecCount2 < FHeader.EventRecCount )
   {
      snprintf(ErrMsg, 2048, "CheckEvent end, RecCount = %d, RecCount2 = %d",
         FHeader.EventRecCount, RecCount2);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      return;
   }

   char ErrStr[200 + 1];
   bool Changed = false;
   FILE *f = fopen(FileName, "rb");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "fopen(%s) fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   fseek(f, sizeof(HJobEvent) * FHeader.EventRecCount, SEEK_SET);

   for ( ; FHeader.EventRecCount < RecCount2; )
   {
      HJobEvent JobEvent;
      fread(&JobEvent, sizeof(HJobEvent), 1, f);
      if ( memcmp(JobEvent.EyeCatcher, "EMPTY", 5) == 0 )
      {
         break;
      }
      if ( memcmp(JobEvent.EyeCatcher, "SIMBAT", 6) != 0 )
      {
         snprintf(ErrMsg, 2048, "File %s, Record %d damaged", FileName, FHeader.EventRecCount + 1);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         break;
      }
      RecLen = sizeof(HJobEvent);
      BkHexToAsc((char*)&JobEvent, HexStr, RecLen);
      HexStr[RecLen * 2] = '\0';
      if ( DbFunc.WriteStatus(SystemName.c_str(), FRecCount, "EVENT", HexStr, ErrStr) != 0 )
      {
         snprintf(ErrMsg, 2048, "WriteStatus(%d) fail, errstr = %s", FRecCount, ErrStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         fclose(f);
         DbDisableFlag = true;
         return;
      }
      FRecCount++;
      FHeader.EventRecCount++;
      Changed = true;
   }

   fclose(f);

   if ( Changed )
   {
      RecLen = sizeof(HDbHeader);
      BkHexToAsc((char*)&FHeader, HexStr, RecLen);
      HexStr[RecLen * 2] = '\0';
      if ( DbFunc.RewriteStatus(SystemName.c_str(), 0, "HEADER", HexStr, ErrStr) != 0 )
      {
         snprintf(ErrMsg, 2048, "RewriteStatus(0) fail, errstr = %s", ErrStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         DbDisableFlag = true;
         return;
      }
   }

   snprintf(ErrMsg, 2048, "CheckEvent end, RecCount = %d, EventRecCount = %d", FRecCount, FHeader.EventRecCount);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
}

void HSyncStatusThread::Execute()
{
   char ErrMsg[2048 + 1];

   sleep(3);
   InitBatchStatus();

   for ( ; !ExitFlag; )
   {
      char Buf[1024];
      int r = BsReadFromSocketMaxLen(FSock, Buf, 1024, 10000);
      if ( r == BS_ETIMEOUT )
      {
         if ( ExitFlag )
         {
            break;
         }
         continue;
      }
      if ( r < 0 )
      {
         if ( errno != EINTR )
         {
            snprintf(ErrMsg, 8192, "BsReadFromSocket fail, r = %d, errno = %d", r, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
         break;
      }
      if ( r == 0 )
      {
         break;
      }
      bool CheckBatchFlag = false;
      bool CheckVarpoolFlag = false;
      bool CheckCtldatFlag = false;
      bool ReloadConfigFlag = false;
      for ( int i = 0; i < r; i++ )
      {
         if ( Buf[i] == 'B' )
         {
            CheckBatchFlag = true;
            break;
         }
         if ( Buf[i] == 'V' )
         {
            CheckVarpoolFlag = true;
         }
         if ( Buf[i] == 'C' )
         {
            CheckCtldatFlag = true;
         }
      }
      if ( CheckBatchFlag )
      {
         CheckBatchStatus();
      }
      if ( CheckBatchFlag || CheckVarpoolFlag )
      {
         CheckVarpool();
      }
      if ( CheckBatchFlag || CheckCtldatFlag )
      {
         CheckCtldat();
      }
      CheckEvent();
   }
}

void HWriteHisThread::Execute()
{
   char ErrMsg[2048 + 1];
   time_t LastErrorTime = 0;

   for ( ; !ExitFlag; BsSleep(10000) )
   {
      time_t CurrTime = time(NULL);
      if ( !HasDbFunc || CurrTime - LastErrorTime < 600 )
      {
         continue;
      }
      char ErrStr[200 + 1];
      HHisInfo Info;
      for ( ; ; )
      {
         FMutex.Lock();
         if ( FIdx >= FHisVec.size() )
         {
            FMutex.Unlock();
            break;
         }
         Info = FHisVec[FIdx];
         FMutex.Unlock();
         char ValueList[200 + 1];
         snprintf(ValueList, sizeof(ValueList), "%s|%s|%d|%s", Info.StartTime.c_str(), Info.EndTime.c_str(),
            Info.Elapse, Info.Status.c_str());
         if ( DbFunc.WriteHis(FSystemName.c_str(), FBatchSeq.c_str(), Info.JobName.c_str(), ValueList, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "WriteHis fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            LastErrorTime = CurrTime;
            DbDisableFlag = true;
            break;
         }
         int n;
         int Elapse[10];
         if ( DbFunc.ReadStatistic(FSystemName.c_str(), FBatchType.c_str(), Info.JobName.c_str(), &n, Elapse, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "ReadStatistic fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            LastErrorTime = CurrTime;
            DbDisableFlag = true;
            break;
         }
         if ( n < 10 )
         {
            Elapse[n++] = Info.Elapse;
         }
         else
         {
            for ( int i = 0; i < 9; i++ )
            {
               Elapse[i] = Elapse[i + 1];
            }
            Elapse[9] = Info.Elapse;
         }
         if ( DbFunc.WriteStatistic(FSystemName.c_str(), FBatchType.c_str(), Info.JobName.c_str(), n, Elapse, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 2048, "WriteStatistic fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            LastErrorTime = CurrTime;
            DbDisableFlag = true;
            break;
         }
         FMutex.Lock();
         FIdx++;
         FMutex.Unlock();
      }
   }
}

void HJobSeq::Init()
{
   FSeq = Ctldat.Seq;
   FQty = 0;
}

void HJobSeq::GetJobSeq(char *JobSeq, int Qty)
{
   char Today[8 + 1];

   BkToday(Today);

   if ( strcmp(Ctldat.SeqDate, Today) != 0 )
   {
      BkStrNCpy(Ctldat.SeqDate, Today, sizeof(Ctldat.SeqDate) - 1);
      FSeq = 0;
      FQty = 0;
   }

   if ( FQty < Qty )
   {
      FQty = Qty + 1000;
      Ctldat.Seq = FSeq + FQty;
      WriteCtldat();
   }

   snprintf(JobSeq, 18 + 1, "%s%010d", Ctldat.SeqDate, FSeq + 1);
   FSeq += Qty;
   FQty -= Qty;
}

void HEventLogger::Init(int FileFd, int FileUsed)
{
   FFileFd = FileFd;
   FFileUsed = FileUsed;
   FFileCapacity = FileUsed;
   FArrayHeader = 0;
   FArrayUsed = 0;
   FLastSyncTime = time(NULL);
}

int HEventLogger::GetFileFd()
{
   return FFileFd;
}

int HEventLogger::GetFileUsed()
{
   FMutex.Lock();
   int Result = FFileUsed;
   FMutex.Unlock();
   return Result;
}

bool HEventLogger::Alloc()
{
   char ErrMsg[2048 + 1];

   if ( FFileCapacity - FFileUsed > ALLOC_STEP / 2 )
   {
      return true;
   }

   bool Fail = false;
   HJobEvent Event;

   snprintf(ErrMsg, 2048, "HEventLogger::Alloc(), Used = %d, Capacity = %d",
      FFileUsed, FFileCapacity);
   Syslog.Log(__FILE__, __LINE__, llInfo, "ENTRY", ErrMsg);

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());

   if ( FFileFd < 0 )
   {
      FFileFd = open(FileName, O_RDWR, 0666);
      if ( FFileFd < 0 )
      {
         return false;
      }
      JobEventFd = FFileFd;
   }
   else
   {
      int TmpFd = open(FileName, O_RDWR, 0666);
      if ( TmpFd < 0 )
      {
         snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(FFileFd);
         FFileFd = -1;
         JobEventFd = FFileFd;
         return false;
      }
      close(TmpFd);
   }

   long Offset = FFileCapacity * sizeof(HJobEvent);

   if ( lseek(FFileFd, Offset, SEEK_SET) == -1 )
   {
      snprintf(ErrMsg, 2048, "lseek(%ld) fail, errno = %d", Offset, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(FFileFd);
      FFileFd = -1;
      JobEventFd = FFileFd;
      return false;
   }

   memset(&Event, 0, sizeof(Event));
   BkStrNCpy(Event.EyeCatcher, "EMPTY", sizeof(Event.EyeCatcher) - 1);

   for ( int i = 0; i < ALLOC_STEP; )
   {
      int j;
      for ( j = 0; j < SYNC_STEP && i < ALLOC_STEP; j++, i++ )
      {
         if ( write(FFileFd, &Event, sizeof(HJobEvent)) < 0 )
         {
            snprintf(ErrMsg, 2048, "write(%d) fail, errno = %d", sizeof(HJobEvent), errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            Fail = true;
            close(FFileFd);
            FFileFd = -1;
            JobEventFd = FFileFd;
            break;
         }
      }
      if ( Fail )
      {
         break;
      }
      if ( fsync(FFileFd) < 0 )
      {
         snprintf(ErrMsg, 2048, "fsync fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         Fail = true;
         close(FFileFd);
         FFileFd = -1;
         JobEventFd = FFileFd;
         break;
      }
      FFileCapacity += j;
   }

   snprintf(ErrMsg, 2048, "HEventLogger::Alloc() return %s", Fail ? "false" : "true");
   Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
   return !Fail;
}

bool HEventLogger::Sync(int &SyncQty)
{
   if ( Batch.Status == bsPaused )
   {
      return false;
   }

   char ErrMsg[2048 + 1];

   SyncQty = 0;
   FLastSyncTime = time(NULL);

   if ( FArrayUsed == 0 )
   {
      return true;
   }

   snprintf(ErrMsg, 2048, "HEventLogger::Sync(), FFileUsed = %d, FFileCapacity = %d, FArrayUsed = %d, FArrayHeader = %d",
      FFileUsed, FFileCapacity, FArrayUsed, FArrayHeader);
   Syslog.Log(__FILE__, __LINE__, llDebug, "ENTRY", ErrMsg);

   time_t WriteBeginTime = time(NULL);
   long Offset = FFileUsed * sizeof(HJobEvent);

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());

   if ( FFileFd < 0 )
   {
      FFileFd = open(FileName, O_RDWR, 0666);
      if ( FFileFd < 0 )
      {
         return false;
      }
      JobEventFd = FFileFd;
   }
   else
   {
      int TmpFd = open(FileName, O_RDWR, 0666);
      if ( TmpFd < 0 )
      {
         snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(FFileFd);
         FFileFd = -1;
         JobEventFd = FFileFd;
         return false;
      }
      close(TmpFd);
   }

   if ( lseek(FFileFd, Offset, SEEK_SET) == -1 )
   {
      snprintf(ErrMsg, 2048, "lseek(%ld) fail, errno = %d", Offset, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(FFileFd);
      FFileFd = -1;
      JobEventFd = FFileFd;
      return false;
   }

   for ( ; FArrayUsed > 0; )
   {
      int n = FArrayUsed;
      if ( FArrayHeader + n > ARRAY_SIZE )
      {
         n = ARRAY_SIZE - FArrayHeader;
      }
      if ( n > SYNC_STEP )
      {
         n = SYNC_STEP;
      }
      if ( write(FFileFd, &FArray[FArrayHeader], sizeof(HJobEvent) * n) < 0 )
      {
         snprintf(ErrMsg, 2048, "write(%d) fail, errno = %d", sizeof(HJobEvent) * n,  errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(FFileFd);
         FFileFd = -1;
         JobEventFd = FFileFd;
         return false;
      }
      if ( fsync(FFileFd) < 0 )
      {
         snprintf(ErrMsg, 2048, "fsync fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(FFileFd);
         FFileFd = -1;
         JobEventFd = FFileFd;
         return false;
      }
      SyncQty += n;
      FArrayHeader += n;
      if ( FArrayHeader == ARRAY_SIZE )
      {
         FArrayHeader = 0;
      }
      FArrayUsed -= n;
      FMutex.Lock();
      FFileUsed += n;
      FMutex.Unlock();
      if ( FFileUsed > FFileCapacity )
      {
         FFileCapacity = FFileUsed;
      }
   }

   int WriteTotalTime = time(NULL) - WriteBeginTime;

   if ( WriteTotalTime > 10 )
   {
      snprintf(ErrMsg, 2048, "job_events writing time = %d", WriteTotalTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
   }

   snprintf(ErrMsg, 2048, "HEventLogger::Sync() complete, FFileUsed = %d, FFileCapacity = %d, FArrayUsed = %d, FArrayHeader = %d",
      FFileUsed, FFileCapacity, FArrayUsed, FArrayHeader);
   Syslog.Log(__FILE__, __LINE__, llDebug, "ENTRY", ErrMsg);

   if ( SyncQty > 0 )
   {
      BsWriteToSocket(SockDb, "J", 1, 0);
      PushBackupObj(boJobEvents);
      DoHotback();
   }

   return Alloc();
}

bool HEventLogger::CheckSync(int &SyncQty)
{
   if ( FArrayUsed > SYNC_STEP || time(NULL) - FLastSyncTime > SYNC_PERIOD )
   {
      return Sync(SyncQty);
   }
   else
   {
      SyncQty = 0;
      return true;
   }
}

bool HEventLogger::Put(const HJobEvent &Event)
{
   char ErrMsg[2048 + 1];

   if ( FArrayUsed == ARRAY_SIZE )
   {
      snprintf(ErrMsg, 2048, "Array full, event abandoned. JobName = %s, PartIdx = %d, Time = %s, "
         "NumOfPart = %d, ServerId = %d, Pid = %d, Status = %s",
         Event.JobName, Event.PartIdx, Event.Time,
         Event.NumOfPart, Event.ServerId, Event.Pid, JobStatusToString(Event.Status));
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   int Idx = FArrayHeader + FArrayUsed;

   if ( Idx >= ARRAY_SIZE )
   {
      Idx -= ARRAY_SIZE;
   }

   memcpy(&FArray[Idx], &Event, sizeof(HJobEvent));
   FArrayUsed++;
   return true;
}

void HCritical::WriteCriticalPath(FILE *f)
{
   InitGraph();
   CalcPath();
   fprintf(f, "\nCritical path : \n\n");
   PrintPath(f);
   fprintf(f, "\n");
}

void HCritical::InitGraph()
{
   CriticalGraph.clear();

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.Type == jtExternJob || Job.Type == jtGroup ||
           Job.Status == jsWaiting  || Job.Status == jsUnknown )
      {
         continue;
      }
      HCritical Node;
      int IdleTime = Job.IdleTimeSum;
      Node.Name = Job.Name;
      Node.Tag = Job.Tag;
      HString StartTime;
      if ( Job.PartFlag )
      {
         StartTime = Job.Xml->GetProperty("START_TIME");
         Node.PartQty = Job.PartVec.size();
      }
      else
      {
         StartTime = Job.Xml->GetProperty("BEGINNING_TIME");
         if ( StartTime == "" )
         {
            StartTime = Job.Xml->GetProperty("START_TIME");
         }
      }
      HString EndTime = Job.Xml->GetProperty("END_TIME");
      if ( EndTime == "" )
      {
         EndTime = CurrentDateTime();
         if ( Job.IdleTimeBegin > 0 )
         {
            IdleTime += time(NULL) - Job.IdleTimeBegin;
         }
      }
      Node.StartTime = StartTime;
      Node.EndTime = EndTime;
      Node.Time = StringToTime(EndTime) - StringToTime(StartTime) - IdleTime;
      if ( Job.Type == jtEventWait || Job.Type == jtTime )
      {
         Node.Time = 0;
      }
      CriticalGraph[Node.Name] = Node;
   }

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      for ( int i = 0; i < Job.ExternJobList.size(); i++ )
      {
         HString &ExternJob = Job.ExternJobList[i];
         if ( strstr(ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            AddLink(ExternJob, Job.Name);
         }
         else
         {
            for ( map<HString, job_info>::iterator Iter2 = Graph.begin();
                  Iter2 != Graph.end(); Iter2++ )
            {
               job_info &Job2 = Iter2->second;
               if ( Job2.GroupId == ExternJob && Job2.Type != jtExternJob && Job2.SuccJobs.empty() )
               {
                  AddLink(Job2.Name, Job.Name);
               }
            }
         }
      }
      for ( int i = 0; i < Job.SuccJobs.size(); i++ )
      {
         AddLink(Job.Name, Job.SuccJobs[i]);
      }
   }

   HCritical Start;
   Start.Name = "START";

   for ( map<HString, HCritical>::iterator Iter = CriticalGraph.begin();
         Iter != CriticalGraph.end(); Iter++ )
   {
      Start.SuccJobs.insert(Iter->first);
   }

   CriticalGraph[Start.Name] = Start;

   for ( map<HString, HCritical>::iterator Iter = CriticalGraph.begin();
         Iter != CriticalGraph.end(); Iter++ )
   {
      HCritical &Critical = Iter->second;
      Critical.SuccJobVec.insert(Critical.SuccJobVec.end(), Critical.SuccJobs.begin(), Critical.SuccJobs.end());
      if ( Critical.SuccJobVec.empty() )
      {
         Critical.CompletedFlag = true;
         Critical.MaxTime = Critical.Time;
      }
   }
}

void HCritical::AddLink(const HString &From, const HString &To)
{
   job_info &JobFrom = Graph[From];
   job_info &JobTo = Graph[To];

   if ( JobFrom.Type == jtExternJob )
   {
      if ( strstr(JobFrom.ExternJob.c_str(), JobNameDelimiter.c_str()) )
      {
         AddLink(JobFrom.ExternJob, To);
      }
      else
      {
         for ( map<HString, job_info>::iterator Iter = Graph.begin();
               Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( Job.GroupId == JobFrom.ExternJob && Job.Type != jtExternJob && Job.SuccJobs.empty() )
            {
               AddLink(Job.Name, To);
            }
         }
      }
   }

   if ( JobFrom.Type == jtGroup )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.GroupId == JobFrom.Group && Job.Type != jtExternJob && Job.SuccJobs.empty() )
         {
            AddLink(Job.Name, To);
         }
      }
      return;
   }

   if ( JobTo.Type == jtGroup )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.GroupId == JobTo.Group && Job.Type != jtExternJob &&
              Job.InDegreeWithinGroup == 0 )
         {
            AddLink(From, Job.Name);
         }
      }
      return;
   }

   if ( CriticalGraph.find(From) == CriticalGraph.end() ||
        CriticalGraph.find(To) == CriticalGraph.end() )
   {
      return;
   }

   CriticalGraph[From].SuccJobs.insert(To);
}

void HCritical::CalcPath()
{
   int StackP = 0;
   vector<HString> JobStack;

   JobStack.push_back("START");

   for ( ; ; )
   {
      HCritical &ThisNode = CriticalGraph[JobStack[StackP]];
      for ( ; ThisNode.SuccJobIdx < ThisNode.SuccJobVec.size(); )
      {
         HString &SuccNodeId = ThisNode.SuccJobVec[ThisNode.SuccJobIdx];
         HCritical &SuccNode = CriticalGraph[SuccNodeId];
         if ( SuccNode.CompletedFlag )
         {
            if ( SuccNode.MaxTime >= ThisNode.MaxTime )
            {
               ThisNode.MaxTime = SuccNode.MaxTime;
               ThisNode.MaxNode = SuccNodeId;
            }
            ThisNode.SuccJobIdx++;
         }
         else
         {
            StackP++;
            if ( StackP == JobStack.size() )
            {
               JobStack.push_back(SuccNodeId);
            }
            else
            {
               JobStack[StackP] = SuccNodeId;
            }
            break;
         }
      }
      if ( ThisNode.SuccJobIdx == ThisNode.SuccJobVec.size() )
      {
         ThisNode.CompletedFlag = true;
         ThisNode.MaxTime += ThisNode.Time;
         if ( StackP == 0 )
         {
            break;
         }
         StackP--;
      }
   }
}

void HCritical::PrintPath(FILE *f)
{
   for ( HString Node = "START"; Node != ""; )
   {
      HCritical &Job = CriticalGraph[Node];
      if ( Node != "START" )
      {
         int GbkLen;
         if ( CodeSet == csUtf8 )
         {
            char GbkStr[200 + 1];
            BkStrNCpy(GbkStr, Job.Name.c_str(), sizeof(GbkStr) - 1);
            ConvString(CodeSet, csGbk, GbkStr);
            GbkLen = strlen(GbkStr);
         }
         else
         {
            GbkLen = Job.Name.length();
         }
         fprintf(f, "%s", Job.Name.c_str());
         if ( GbkLen < 40 )
         {
            fprintf(f, "%*s", 40 - GbkLen, "");
         }

         fprintf(f, "|%.4s/%.2s/%.2s.%.2s:%.2s:%.2s|%.4s/%.2s/%.2s.%.2s:%.2s:%.2s|%02d:%02d:%02d|%5d|%s\n",
            Job.StartTime.c_str(), Job.StartTime.c_str() + 4, Job.StartTime.c_str() + 4 + 2,
            Job.StartTime.c_str() + 4 + 2 + 2, Job.StartTime.c_str() + 4 + 2 + 2 + 2, Job.StartTime.c_str() + 4 + 2 + 2 + 2 + 2,
            Job.EndTime.c_str(), Job.EndTime.c_str() + 4, Job.EndTime.c_str() + 4 + 2,
            Job.EndTime.c_str() + 4 + 2 + 2, Job.EndTime.c_str() + 4 + 2 + 2 + 2, Job.EndTime.c_str() + 4 + 2 + 2 + 2 + 2,
            Job.Time / 3600, Job.Time % 3600 / 60, Job.Time % 60, Job.PartQty, Job.Tag.c_str());
      }
      Node = Job.MaxNode;
   }
}

void HCalcSection::Calc()
{
   set<HString> OutputSet;
   HString CurrentSection;

   for ( ; ; )
   {
      set<HString> JobSet;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.InDegree > 0 || OutputSet.find(Job.Name) != OutputSet.end() )
         {
            continue;
         }
         JobSet.insert(Job.Name);
      }
      if ( JobSet.empty() )
      {
         break;
      }
      for ( set<HString>::iterator SetIter = JobSet.begin();
            SetIter != JobSet.end(); SetIter++ )
      {
         job_info &Job = Graph[*SetIter];
         OutputSet.insert(Job.Name);
         for ( set<HString>::iterator SetIter = Job.SuccJobs.begin();
               SetIter != Job.SuccJobs.end(); SetIter++ )
         {
            Graph[*SetIter].InDegree--;
         }
         if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
              Job.Type != jtTime && Job.Type != jtBatch )
         {
            continue;
         }
         for ( group_info *GroupPtr = ::Graph[Job.Name].GroupPtr; GroupPtr != NULL; )
         {
            if ( SectionSet.find(GroupPtr->Name) != SectionSet.end() )
            {
               CurrentSection = GroupPtr->Name;
               SectionSet.erase(GroupPtr->Name);
            }
            if ( GroupPtr->AgentPtr == NULL )
            {
               GroupPtr = NULL;
            }
            else
            {
               GroupPtr = GroupPtr->AgentPtr->GroupPtr;
            }
         }
         if ( SectionSet.find(Job.Name) != SectionSet.end() )
         {
            CurrentSection = Job.Name;
            SectionSet.erase(Job.Name);
         }
         ::Graph[Job.Name].Section = CurrentSection;
      }
   }
}

bool HSystem::Init()
{
   int r = 0;
   char Command[8192 + 1];
   char ErrMsg[2048 + 1];

   r = socketpair(PF_UNIX, SOCK_STREAM, 0, FFdArr);

   if ( r == -1 )
   {
      snprintf(ErrMsg, 2048, "socketpair fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   pid_t Pid = fork();

   if ( Pid == -1 )
   {
      snprintf(ErrMsg, 2048, "fork fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   if ( Pid == 0 )
   {
      close(SockSch);
      close(FFdArr[1]);
      for ( ; ; )
      {
         int Len = 0;
         char LenStr[80 + 1];
         int r;
         r = BsReadFromSocket(FFdArr[0], LenStr, 1 + 8, 10);
         if ( r == BS_ETIMEOUT )
         {
            continue;
         }
         if ( r == 0 )
         {
            break;
         }
         if ( r < 1 + 8 )
         {
            snprintf(ErrMsg, 2048, "BsReadFromSocket fail, r = %d, errno = %d", r, errno);
            Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
            break;
         }
         LenStr[1 + 8] = '\0';
         Len = StrToInt(LenStr + 1);
         Command[0] = '\0';
         if ( Len > 0 )
         {
            r = BsReadFromSocket(FFdArr[0], Command, Len, 10000);
            if ( r != Len )
            {
               snprintf(ErrMsg, 2048, "BsReadFromSocket(%d) fail, r = %d, errno = %d",
                   Len, r, errno);
               Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
               break;
            }
            Command[r] = '\0';
         }
         snprintf(ErrMsg, 2048, "Func(%c), cmd = %s", LenStr[0], Command);
         Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
         if ( LenStr[0] == '0' )
         {
            BkSignal(SIGCHLD, SIG_DFL, 0);
            r = system(Command);
            BkSignalIgnore(SIGCHLD);
            r = BsWriteToSocket(FFdArr[0], &r, sizeof(int), 10000);
            if ( r != 0 )
            {
               snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
               Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
               break;
            }
            continue;
         }
         if ( LenStr[0] == '2' )
         {
            pid_t Pid = fork();
            if ( Pid == -1 )
            {
               snprintf(ErrMsg, 2048, "fork fail, errno = %d", errno);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               continue;
            }
            if ( Pid == 0 )
            {
               close(FFdArr[0]);
               BkSignal(SIGCHLD, SIG_DFL, 0);
               Announce();
               exit(0);
            }
            continue;
         }
      }
      exit(0);
   }

   close(FFdArr[0]);
   snprintf(ErrMsg, 2048, "HSystem::Init, pid = %d", Pid);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
   return true;
}

void HSystem::Close()
{
   close(FFdArr[1]);
}

bool HSystem::WriteRequest(char FuncCode, const char *ArgStr)
{
   int r = 0;
   int Len = strlen(ArgStr);
   char LenStr[80 + 1];
   char ErrMsg[2048 + 1];

   snprintf(LenStr, 80, "%c%08d", FuncCode, Len);

   r = BsWriteToSocket(FFdArr[1], LenStr, 1 + 8, 10000);

   if ( r != 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
      return false;
   }

   r = BsWriteToSocket(FFdArr[1], ArgStr, Len, 10000);

   if ( r != 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket(%d) fail, r = %d, errno = %d", Len, r, errno);
      Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
      return false;
   }

   return true;
}

int HSystem::Run(const char *Command)
{
   int r;
   char ErrMsg[2048 + 1];

   if ( !WriteRequest('0', Command) )
   {
      ExitFlag = true;
      return __W_EXITCODE(8, 0);
   }

   int Result = 0;

   r = BsReadFromSocket(FFdArr[1], &Result, sizeof(int), BS_TIME_INFINITE);

   if ( r != sizeof(int) )
   {
      snprintf(ErrMsg, 2048, "BsReadFromSocket(%d) fail, r = %d, errno = %d", sizeof(int), r, errno);
      Syslog.Log(__FILE__, __LINE__, llFatal, "ONERR", ErrMsg);
      ExitFlag = true;
      return __W_EXITCODE(8, 0);
   }

   return Result;
}

void HSystem::RunAnnounce()
{
   WriteRequest('2', "");
}

void HSystem::Announce()
{
   pid_t ParentPid = getppid();
   bool OkFlag = true;
   HString Result;
   char AddrStr[200 + 1];
   char TmpStr[500 + 1];
   char ErrMsg[2048 + 1];
   int i = 0;

   for ( i = 0; i < ClusterNodeVec.size(); i++ )
   {
      HClusterNodeInfo &Node = ClusterNodeVec[i];
      if ( i == MyId )
      {
         continue;
      }
      if ( !AscendAnnounce(Node.Addr, Node.Port) )
      {
         OkFlag = false;
         break;
      }
   }

   if ( HotbackAddr != "" )
   {
      if ( !AscendAnnounce(HotbackAddr, HotbackPort) )
      {
         OkFlag = false;
      }
   }

   snprintf(AddrStr, 200, "%s:%d", "127.0.0.1", LocalPort);
   snprintf(TmpStr, 500, "W|||%s", OkFlag ? "Y" : "N");

   for ( i = 0; i < 60; i++ )
   {
      if ( kill(ParentPid, 0) == -1 )
      {
         break;
      }
      snprintf(ErrMsg, 2048, "CallServer(%s, ANNOUNCE_COMPLETE)", AddrStr);
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      bool r = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "ANNOUNCE_COMPLETE", "",
         TmpStr, true, Result, true);
      if ( !r || Result != "0" )
      {
         sleep(10);
         continue;
      }
      break;
   }

   if ( i == 60 )
   {
      snprintf(ErrMsg, 2048, "Send ANNOUNCE_COMPLETE, retry %d times fail", i);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   }
}

void HListenerThread::Execute()
{
   char ErrMsg[2048 + 1];

   for ( ; !ExitFlag; )
   {
      struct timeval CurrTime;
      gettimeofday(&CurrTime, NULL);
      HInputInfo Input;
      int r = GetInput(Input);
      Input.ThreadIdx = FIdx;
      if ( r == -1 )
      {
         sleep(5);
         continue;
      }
      if ( ( r == 0 && Input.Func == fcTouch ) || r == -3 )
      {
         continue;
      }
      Input.ArriveTime = CurrTime.tv_sec;
      if ( MyRole == nrMaster &&
           ( Input.Func == fcExecuteCommand || Input.Func == fcKillProcess ||
             Input.Func == fcTouchGateway || Input.Func == fcInquiryCommand ||
             Input.Func == fcDatabase || Input.Func == fcBackupInit ||
             Input.Func == fcPushAlarm || Input.Func == fcPushOutline ) )
      {
         snprintf(ErrMsg, 2048, "This node is master, Input.Func = %s, Object = %s, IP = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str(),
            Input.IpAddr.c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         continue;
      }
      if ( MyRole == nrSlave &&
           ( Input.Func != fcMasterAscend && Input.Func != fcReloadConfig &&
             Input.Func != fcExecuteCommand && Input.Func != fcKillProcess &&
             Input.Func != fcInquiryCommand && Input.Func != fcSetVar &&
             Input.Func != fcViewLog && Input.Func != fcViewAdditionalLog &&
             Input.Func != fcJobSuccess && Input.Func != fcJobFail &&
             Input.Func != fcBackupFile &&
             Input.Func != fcDump ) )
      {
         snprintf(ErrMsg, 2048, "This node is slave, Input.Func = %s, Object = %s, IP = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str(),
            Input.IpAddr.c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         continue;
      }
      if ( MyRole == nrBackup &&
           ( Input.Func != fcMasterAscend && Input.Func != fcReloadConfig &&
             Input.Func != fcBackupInit && Input.Func != fcBackupFile &&
             Input.Func != fcDump ) )
      {
         snprintf(ErrMsg, 2048, "This node is backup, Input.Func = %s, Object = %s, IP = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str(),
            Input.IpAddr.c_str());
         if ( Input.Func != fcSlaveSignon && Input.Func != fcInquiry && Input.Func != fcOutline )
         {
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         }
         ReturnFail(Input, ErrMsg);
         continue;
      }
      if ( MyRole == nrGateway &&
           ( Input.Func != fcTouchGateway && Input.Func != fcReloadConfig &&
             Input.Func != fcDownload && Input.Func != fcDatabase &&
             Input.Func != fcPushAlarm && Input.Func != fcPushOutline &&
             Input.Func != fcGetVar && Input.Func != fcSetVar &&
             Input.Func != fcDump ) )
      {
         snprintf(ErrMsg, 2048, "This node is gateway, Input.Func = %s, Object = %s, IP = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str(),
            Input.IpAddr.c_str());
         ReturnFail(Input, ErrMsg);
         continue;
      }
      HLogLevel LogLevel = Input.Func == fcSlaveSignon || Input.Func == fcInquiry ||
         Input.Func == fcJobSuccess || Input.Func == fcJobRunning ||
         Input.Func == fcExecuteCommand || Input.Func == fcLogin ||
         Input.Func == fcOutline || 
         Input.Func == fcBackupFile || Input.Func == fcTouchGateway ||
         Input.Func == fcDatabase || Input.Func == fcPushOutline ? llDebug : llInfo;
      if ( Syslog.CheckLevel(LogLevel) )
      {
         snprintf(ErrMsg, 2048, "Input.Func = %s, Object = %s, IP = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str(),
            Input.IpAddr.c_str());
         Syslog.Log(__FILE__, __LINE__, LogLevel, "ENTRY", ErrMsg);
      }
      if ( Input.Func != fcSlaveSignon &&
           Input.Func != fcTouchGateway &&
           Input.Func != fcInquiry &&
           Input.Func != fcOutline &&
           !( Input.Func >= fcJobCancel && Input.Func <= fcJobUnknown )
         )
      {
         GraphMutex.Lock();
         LastActionTime = CurrTime.tv_sec;
         GraphMutex.Unlock();
      }
      if ( Input.Func >= fcJobCancel && Input.Func <= fcJobUnknown )
      {
         ReturnSuccess(Input);
         HStatusInputInfo JobStatusInput;
         JobStatusInput.Func = Input.Func;
         BkStrNCpy(JobStatusInput.FullName, Input.ObjName.c_str(), sizeof(JobStatusInput.FullName) - 1);
         HString JobName;
         JobStatusInput.PartFlag = SplitPartId(Input.ObjName, JobName, JobStatusInput.PartIdx);
         if ( MyRole == nrMaster )
         {
            if ( Graph.find(JobName) == Graph.end() )
            {
               snprintf(ErrMsg, 2048, "Job %s not exist", JobName.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
               continue;
            }
            JobStatusInput.JobPtr = &Graph[JobName];
         }
         else
         {
            JobStatusInput.JobPtr = NULL;
         }
         if ( Input.ArgVec.size() > 4 )
         {
            JobStatusInput.JobStartTime = StrToLong(Input.ArgVec[4].c_str());
         }
         if ( Input.ArgVec.size() > 5 )
         {
            JobStatusInput.Pid = StrToInt(Input.ArgVec[5].c_str());
         }
         if ( Input.ArgVec.size() > 6 )
         {
            JobStatusInput.SetConditionFlag = ( Input.ArgVec[6] == "Y" );
         }
         BkStrNCpy(JobStatusInput.IpAddr, Input.IpAddr.c_str(), sizeof(JobStatusInput.IpAddr) - 1);
         for ( ; !ExitFlag && !StatusInputQueue.TimedPushBack(JobStatusInput, 10000); )
            ;
         continue;
      }
      if ( Input.Func == fcCalcCriticalPath ||
           Input.Func == fcDownload ||
           Input.Func == fcFireEvent ||
           Input.Func == fcInquiry ||
           Input.Func == fcDatabase ||
           Input.Func == fcLogin ||
           Input.Func == fcOutline ||
           Input.Func == fcUploadSbt ||
           Input.Func == fcViewAdditionalLog ||
           Input.Func == fcViewLog ||
           Input.Func == fcViewSummary ||
           Input.Func == fcGetSeq ||
           Input.Func == fcGetVar ||

           Input.Func == fcExecuteCommand ||
           Input.Func == fcInquiryCommand ||
           Input.Func == fcKillProcess ||

           Input.Func == fcPushOutline ||

           Input.Func == fcBackupInit ||
           Input.Func == fcBackupFile )
      {
         FuncArray[Input.Func](Input);
         HLogLevel LogLevel = Input.Func == fcSlaveSignon || Input.Func == fcInquiry ||
            Input.Func == fcJobSuccess || Input.Func == fcJobRunning ||
            Input.Func == fcExecuteCommand || Input.Func == fcLogin ||
            Input.Func == fcOutline ||
            Input.Func == fcBackupFile || Input.Func == fcTouchGateway ||
            Input.Func == fcDatabase || Input.Func == fcPushOutline ? llDebug : llInfo;
         if ( Syslog.CheckLevel(LogLevel) )
         {
            snprintf(ErrMsg, 2048, "Process completed, Func = %s, Object = %s",
               GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str());
            Syslog.Log(__FILE__, __LINE__, LogLevel, "EXIT", ErrMsg);
         }
         continue;
      }
      for ( ; !ExitFlag && !InputQueue.TimedPushBack(Input, 10000); )
         ;
   }
}

template<typename T>void HWaitingQueue<T>::Init(int QueueLength)
{
   FHeader = 0;
   FQty = 0;
   FCond = new HThreadCond(&FMutex);
   FQueueLength = QueueLength;
   FQueue = new T[QueueLength];
}

template<typename T>void HWaitingQueue<T>::Close()
{
   delete FCond;
   delete[] FQueue;
}

template<typename T>bool HWaitingQueue<T>::TimedPushBack(const T &Info, int Millisecond)
{
   FMutex.Lock();

   for ( ; ; )
   {
      if ( FQty == FQueueLength )
      {
         if ( Millisecond == 0 || !FCond->TimedWait(Millisecond) )
         {
            FMutex.Unlock();
            return false;
         }
      }
      else
      {
         break;
      }
   }

   int Idx = FHeader + FQty;

   if ( Idx >= FQueueLength )
   {
      Idx -= FQueueLength;
   }

   FQueue[Idx] = Info;
   FQty++;
   FMutex.Unlock();
   return true;
}

template<typename T>bool HWaitingQueue<T>::TimedPushBackArray(const T Info[], int &Qty, int Millisecond)
{
   FMutex.Lock();

   for ( ; ; )
   {
      if ( FQty == FQueueLength )
      {
         if ( Millisecond == 0 || !FCond->TimedWait(Millisecond) )
         {
            FMutex.Unlock();
            return false;
         }
      }
      else
      {
         break;
      }
   }

   for ( ; Qty > 0; Qty-- )
   {
      int Idx = FHeader + FQty;
      if ( Idx >= FQueueLength )
      {
         Idx -= FQueueLength;
      }
      FQueue[Idx] = Info[Qty - 1];
      FQty++;
   }

   FMutex.Unlock();
   return true;
}

template<typename T>bool HWaitingQueue<T>::TimedPop(T &Info, int Millisecond)
{
   FMutex.Lock();

   for ( ; ; )
   {
      if ( FQty == 0 )
      {
         if ( Millisecond == 0 || !FCond->TimedWait(Millisecond) )
         {
            FMutex.Unlock();
            return false;
         }
      }
      else
      {
         break;
      }
   }

   Info = FQueue[FHeader++];

   if ( FHeader == FQueueLength )
   {
      FHeader = 0;
   }

   FQty--;
   FMutex.Unlock();
   return true;
}

template<typename T>void HWaitingQueue<T>::GetCurrForRead(int &Header, int &Qty)
{
   FMutex.Lock();
   Header = FHeader;
   Qty = FQty;
   FMutex.Unlock();
}

template<typename T>void HWaitingQueue<T>::Remove(int Qty)
{
   FMutex.Lock();
   FHeader += Qty;

   if ( FHeader >= FQueueLength )
   {
      FHeader -= FQueueLength;
   }

   FQty -= Qty;
   FMutex.Unlock();
   FCond->Broadcast();
}

template<typename T>void HWaitingQueue<T>::GetCurrForWrite(int &Header, int &Qty)
{
   FMutex.Lock();
   Header = FHeader + FQty;

   if ( FHeader >= FQueueLength )
   {
      FHeader -= FQueueLength;
   }

   Qty = FQueueLength - FQty;
   FMutex.Unlock();
}

template<typename T>void HWaitingQueue<T>::Add(int Qty)
{
   FMutex.Lock();
   FQty += Qty;
   FMutex.Unlock();
   FCond->Broadcast();
}

template<typename T>bool HWaitingQueue<T>::MasterPushBackNoWait(const T &Info)
{
   int Header;
   int Qty;

   GetCurrForWrite(Header, Qty);

   if ( Qty == 0 )
   {
      return false;
   }

   GetByIdx(Header) = Info;
   Add(1);
   return true;
}

template<typename T>const T &HWaitingQueue<T>::GetByIdx(int Idx) const
{
   if ( Idx >= FQueueLength )
   {
      Idx -= FQueueLength;
   }

   return FQueue[Idx];
}

template<typename T>T &HWaitingQueue<T>::GetByIdx(int Idx)
{
   if ( Idx >= FQueueLength )
   {
      Idx -= FQueueLength;
   }

   return FQueue[Idx];
}

// === end of class method ===

// === function ===

static void SignalHandler(int Signo)
{
   char ErrMsg[500 + 1];

   snprintf(ErrMsg, 500, "SIG(%d) catched, LastActionPoint = 0x%08x", Signo, LastActionPoint);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_main = 0x%08x", LastActionPoint_main);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_Init = 0x%08x", LastActionPoint_Init);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_DoIt = 0x%08x", LastActionPoint_DoIt);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_Schedule = 0x%08x", LastActionPoint_Schedule);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_RunVec = 0x%08x", LastActionPoint_RunVec);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_ChangeStatus = 0x%08x", LastActionPoint_ChangeStatus);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_StartBatch = 0x%08x", LastActionPoint_StartBatch);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   snprintf(ErrMsg, 500, "LastActionPoint_Load = 0x%08x", LastActionPoint_Load);
   BkPrintError("%s", ErrMsg);
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);

#ifdef BK_LINUX
   {
      void *PtrArray[100];
      int n = backtrace(PtrArray, 100);
      backtrace_symbols_fd(PtrArray, n, 2);
   }
#endif

   DoDump();
   exit(12);
}

static HString CalcNextScheduleDate(const HString &Date)
{
   bool Found = false;
   HString NextDate;

   for ( set<HString>::iterator Iter = ScheduleDateSet.begin();
         Iter != ScheduleDateSet.end(); Iter++ )
   {
      if ( *Iter == Date )
      {
         Iter++;
         if ( Iter != ScheduleDateSet.end() )
         {
            Found = true;
            NextDate = *Iter;
         }
         break;
      }
   }
   if ( !Found )
   {
      NextDate = TimeToString(StringToTime(Date) + 24 * 60 * 60).substr(0, 8);
   }

   return NextDate;
}

static int GatewayPushAlarm(const char *InfoStr, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "p|%s||%s|%s", CodeSetToStr(CodeSet).c_str(), SystemName.c_str(), InfoStr);

   bool b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "PUSH_ALARM", "", TmpStr, true, ResultStr);

   if ( !b && GatewayAddrVec.size() > 1 )
   {
      GatewayIdx = 1 - GatewayIdx;
      AddrStr = GatewayAddrVec[GatewayIdx];
      b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "PUSH_ALARM", "", TmpStr, true, ResultStr);
   }

   if ( !b )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayPushOutline(const char *InfoStrArray[], int Qty, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString AddrStr = GatewayAddrVec[GatewayIdx];
   const char *InfoStr = InfoStrArray[0];

   snprintf(TmpStr, 4096, "q|%s||%s", CodeSetToStr(CodeSet).c_str(), InfoStr);

   bool b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "PUSH_OUTLINE", "", TmpStr, true, ResultStr);

   if ( !b && GatewayAddrVec.size() > 1 )
   {
      GatewayIdx = 1 - GatewayIdx;
      AddrStr = GatewayAddrVec[GatewayIdx];
      b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "PUSH_OUTLINE", "", TmpStr, true, ResultStr);
   }

   if ( !b )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static bool CreateEmptyFile(const HString &FileName, char ErrMsg[2048 + 1])
{
   int Fd = open(FileName.c_str(), O_CREAT|O_WRONLY|O_SYNC|O_TRUNC, 0666);

   if ( Fd < 0 )
   {
      snprintf(ErrMsg, 2048, "Create %s fail, errno = %d", FileName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   if ( close(Fd) < 0 )
   {
      snprintf(ErrMsg, 2048, "Create %s close fail, errno = %d", FileName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   return true;
}

static bool CreateEmptyXmlFile(const HString &FileName, char ErrMsg[2048 + 1])
{
   int Fd = open(FileName.c_str(), O_CREAT|O_WRONLY|O_SYNC|O_TRUNC, 0666);

   if ( Fd < 0 )
   {
      snprintf(ErrMsg, 2048, "Create %s fail, errno = %d", FileName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   char TmpStr[500 + 1];

   snprintf(TmpStr, 500, "<ROOT>\n</ROOT>\n");
   write(Fd, TmpStr, strlen(TmpStr));

   if ( close(Fd) < 0 )
   {
      snprintf(ErrMsg, 2048, "Create %s close fail, errno = %d", FileName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   return true;
}

static HString PartIdxString(int PartIdx)
{
   char TmpStr[80 + 1];

   snprintf(TmpStr, 80, "%05d", PartIdx + 1);
   return HString(TmpStr);
}

static bool SplitPartId(const HString &FullName, HString &JobName, int &PartIdx)
{
   const char *s = FullName.c_str();

   for ( ; *s != JobNameDelimiter[0] && *s != '\0'; s++ )
      ;

   if ( *s == JobNameDelimiter[0] )
   {
      s++;
   }

   for ( ; *s != JobNameDelimiter[0] && *s != '\0'; s++ )
      ;

   JobName = HString(FullName.c_str(), s - FullName.c_str());

   if ( *s == JobNameDelimiter[0] )
   {
      PartIdx = StrToInt(s + 1) - 1;
      return true;
   }
   else
   {
      PartIdx = -1;
      return false;
   }
}

static HAlarmTimeType StringToAlarmTimeType(const HString &String)
{
   if ( String == "At the moment" || String == "1" )
   {
      return attAtTheMoment;
   }

   if ( String == "Duration from the batch starting" || String == "2" )
   {
      return attDurationFromTheBatchStarting;
   }

   if ( String == "Duration from the job starting" || String == "3" )
   {
      return attDurationFromTheJobStarting;
   }

   return attNone;
}

static HString GetEnv(const HString &VarName)
{
   char *Env = getenv(VarName.c_str());
   HString Result;

   if ( Env == NULL )
      Result =  "";
   else
      Result =  Env;

   return Result;
}

static HString ToShellArg(const HString &Str)
{
   char Result[8192 + 1];

   const char *From;
   char *To = Result;

   for ( From = Str.c_str(); *From != '\0'; )
   {
      if ( *From == '\'' )
      {
         *To++ = '\'';
         *To++ = '\\';
         *To++ = '\'';
         *To++ = '\'';
         From++;
      }
      else
      {
         *To++ = *From++;
      }
   }

   *To = '\0';
   return HString(Result);
}

static const char *FuncCodeStrings[] =
{
   "ANNOUNCE_COMPLETE",
   "BACKUP_INIT",
   "BACKUP_FILE",
   "BREAKPOINT",
   "CALC_CRITICAL_PATH",
   "CHANGE_DATE",
   "CHANGE_JOB_CONDITION",
   "CONFIGURE",
   "DATABASE",
   "DOWNLOAD",
   "DOWNSHIFT",
   "DUMP",
   "EXECUTE_COMMAND",
   "FIRE_EVENT",
   "GET_SEQ",
   "GET_VAR",
   "INQUIRY",
   "INQUIRY_COMMAND",
   "KILL_PROCESS",
   "LOGIN",
   "MASTER_ASCEND",
   "NEXT_BATCH",
   "OUTLINE",
   "PAUSE_BATCH",
   "PUSH_ALARM",
   "PUSH_OUTLINE",
   "RELOAD_CONFIG",
   "RERUN_JOB",
   "RESTART_JOB",
   "RESTORE_JOB",
   "RESTORE_JOB_FAIL",
   "RESTORE_JOB_SUCCESS",
   "RESUME_BATCH",
   "SET_PARA_JOB_NUM",
   "SET_VAR",
   "SKIP_JOB",
   "SLAVE_SIGNON",
   "START_BATCH",
   "STEP",
   "STOP_BATCH",
   "STOP_JOB",
   "STOP_AND_SKIP_JOB",
   "TOUCH_GATEWAY",
   "UPLOAD_SBT",
   "VIEW_ADDITIONAL_LOG",
   "VIEW_LOG",
   "VIEW_SUMMARY",

   "TOUCH",
   "JOB_CANCEL",
   "JOB_FAIL",
   "JOB_RUNNING",
   "JOB_SUCCESS",
   "JOB_UNKNOWN",
};

static const char *GetFuncCodeString(HFuncCode FuncCode)
{
   if ( FuncCode < fcBEGIN || FuncCode > fcEND )
   {
      return "";
   }
   else
   {
      return FuncCodeStrings[FuncCode];
   }
}

static bool ConvFile(HCodeSet From, HCodeSet To,
   const HString &FromFile, const HString &ToFile)
{
   FILE *fin;
   FILE *fout;
   char Line[4096 + 1];
   char ErrMsg[2048 + 1];

   fin = fopen(FromFile.c_str(), "rt");

   if ( fin == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FromFile.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   fout = fopen(ToFile.c_str(), "wt");

   if ( fout == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not create %s, errno = %d", ToFile.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      fclose(fin);
      return false;
   }

   for ( ; ; )
   {
      if ( ReadLine(fin, Line, 4096) == -1 )
      {
         break;
      }
      ConvString(From, To, Line);
      fprintf(fout, "%s\n", Line);
   }

   fclose(fout);
   fclose(fin);
   return true;
}

static bool WriteFileToSocket(int Sock, const char *FileName)
{
   char ErrMsg[2048 + 1];
   FILE *f = fopen(FileName, "rb");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Open %s fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      BsWriteToSocket(Sock, "00000000", 8, COMM_TIME_OUT);
      return false;
   }

   int r;
   int FileLength;
   char Buf[1024 + 1];

   fseek(f, 0, SEEK_END);
   FileLength = ftell(f);
   snprintf(Buf, 500, "%08x", FileLength);
   fseek(f, 0, SEEK_SET);
   r = BsWriteToSocket(Sock, Buf, 8, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
      fclose(f);
      return false;
   }

   for ( ; ; )
   {
      r = fread(Buf, 1, 1024, f);
      if ( r <= 0 )
      {
         break;
      }
      r = BsWriteToSocket(Sock, Buf, r, COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
         fclose(f);
         return false;
      }
   }

   fclose(f);
   return true;
}

static bool ConvAndCompressFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, bool CompressFlag)
{
   char ErrMsg[2048 + 1];
   const long BufSize = 1024 * 1024L;
   char *Buf1 = NULL;
   char *Buf2 = NULL;
   FILE *f = NULL;
   bool Result = false;
   bool ConvFlag = ( FromCodeSet != csNone && ToCodeSet != csNone && FromCodeSet != ToCodeSet );
   int Buf1Offset = 0;
   int r = 0;
   char TmpBuf[80 + 1];
   long FileSize = BkFileSize(FileName);

   Buf1 = (char*)malloc(BufSize + 1024 * 4);
   Buf2 = (char*)malloc(BufSize + 1024 * 4);

   if ( Buf1 == NULL || Buf2 == NULL )
   {
      snprintf(ErrMsg, 2048, "malloc(%d) fail", BufSize * 2);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto end;
   }

   f = fopen(FileName, "rt");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto end;
   }

   snprintf(TmpBuf, 80, "R%s%06ld", CompressFlag ? "Y" : "N", FileSize / ( 1024L * 1024L ));
   r = BsWriteToSocket(Sock, TmpBuf, 8, COMM_TIME_OUT);

   if ( r != 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket(8) fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
      goto end;
   }

   for ( ; ; )
   {
      bool EofFlag = false;
      char *ReadBuf = Buf1;
      int ReadBufLen = 0;
      char *ConvBuf = Buf2;
      int ConvBufLen = 0;
      for ( ; ; )
      {
         r = ReadLine(f, ReadBuf, 1024);
         if ( r == -1 )
         {
            EofFlag = true;
            break;
         }
         if ( ConvFlag )
         {
            int Len = 0;
            if ( FromCodeSet == csUtf8 )
            {
               switch ( ToCodeSet )
               {
               case csGbk :
                  Len = ConvStr(ConvBuf, ReadBuf, -1, UTF_8_TO_GBK);
                  break;
               case csBig5 :
                  Len = ConvStr(ConvBuf, ReadBuf, -1, UTF_8_TO_BIG5HK);
                  break;
               }
               ConvBuf += Len;
               *ConvBuf++ = '\n';
               ConvBufLen += Len + 1;
            }
            else if ( ToCodeSet == csUtf8 )
            {
               switch ( FromCodeSet )
               {
               case csGbk :
                  Len = ConvStr(ConvBuf, ReadBuf, -1, GBK_TO_UTF_8);
                  break;
               case csBig5 :
                  Len = ConvStr(ConvBuf, ReadBuf, -1, BIG5HK_TO_UTF_8);
                  break;
               }
               ConvBuf += Len;
               *ConvBuf++ = '\n';
               ConvBufLen += Len + 1;
            }
            else
            {
               switch ( FromCodeSet )
               {
               case csGbk :
                  ConvStr(ConvBuf, ReadBuf, -1, GBK_TO_UTF_8);
                  break;
               case csBig5 :
                  ConvStr(ConvBuf, ReadBuf, -1, BIG5HK_TO_UTF_8);
                  break;
               }
               switch ( ToCodeSet )
               {
               case csGbk :
                  Len = ConvStr(ReadBuf, ConvBuf, -1, UTF_8_TO_GBK);
                  break;
               case csBig5 :
                  Len = ConvStr(ReadBuf, ConvBuf, -1, UTF_8_TO_BIG5HK);
                  break;
               }
               ReadBuf += Len;
               *ReadBuf++ = '\n';
               ReadBufLen += Len + 1;
            }
         }
         else
         {
            ReadBuf += r;
            *ReadBuf++ = '\n';
            ReadBufLen += r + 1;
         }
         if ( ReadBufLen > BufSize || ConvBufLen > BufSize )
         {
            break;
         }
      }
      char *OutputBuf = NULL;
      int OutputBufLen = 0;
      if ( !ConvFlag || ( FromCodeSet != csUtf8 && ToCodeSet != csUtf8 ) )
      {
         OutputBuf = Buf1;
         OutputBufLen = ReadBufLen;
      }
      else
      {
         OutputBuf = Buf2;
         OutputBufLen = ConvBufLen;
      }
      if ( OutputBufLen == 0 )
      {
         break;
      }
      if ( CompressFlag )
      {
         char *CompressBuf = OutputBuf == Buf1 ? Buf2 : Buf1;
         int CompressBufLen = 0;
         Lz77CompressBuf(OutputBuf, CompressBuf, OutputBufLen, &CompressBufLen);
         OutputBuf = CompressBuf;
         OutputBufLen = CompressBufLen;
      }
      snprintf(TmpBuf, 80, "%08x", OutputBufLen);
      r = BsWriteToSocket(Sock, TmpBuf, 8, COMM_TIME_OUT);
      if ( r != 0 )
      {
         snprintf(ErrMsg, 2048, "BsWriteToSocket(8) fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
         goto end;
      }
      r = BsWriteToSocket(Sock, OutputBuf, OutputBufLen, COMM_TIME_OUT);
      if ( r != 0 )
      {
         snprintf(ErrMsg, 2048, "BsWriteToSocket(%d) fail, r = %d, errno = %d", OutputBufLen, r, errno);
         Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
         goto end;
      }
      if ( EofFlag )
      {
         break;
      }
   }

   snprintf(TmpBuf, 80, "00000000");
   r = BsWriteToSocket(Sock, TmpBuf, 8, COMM_TIME_OUT);

   if ( r != 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket(8) fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
      goto end;
   }

   Result = true;

end :

   if ( Buf1 != NULL )
   {
      free(Buf1);
   }

   if ( Buf2 != NULL )
   {
      free(Buf2);
   }

   if ( f != NULL )
   {
      fclose(f);
   }

   return Result;
}

static bool CompressFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, bool Large, int ThreadIdx)
{
   bool Result = true;
   bool TmpFileFlag1 = false;
   bool TmpFileFlag2 = false;
   char FileName1[500 + 1];
   char FileName2[500 + 1];
   char CurrFileName[500 + 1];
   long FileSize = BkFileSize(FileName);
   char ErrMsg[2048 + 1];
   const long MaxSize = 2 * 1024L * 1024L;

   snprintf(FileName1, 500, "tmpfile1_%d_%d", getpid(), ThreadIdx);
   snprintf(FileName2, 500, "tmpfile2_%d_%d", getpid(), ThreadIdx);

   if ( Large && FileSize > MaxSize )
   {
      FILE *fin = fopen(FileName, "rb");
      if ( fin == NULL )
      {
         snprintf(ErrMsg, 2048, "Open %s fail, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         goto error;
      }
      FILE *fout = fopen(FileName1, "wb");
      if ( fout == NULL )
      {
         snprintf(ErrMsg, 2048, "Create %s fail, errno = %d", __LINE__, FileName1, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         fclose(fin);
         goto error;
      }
      char Buf[1024 + 1];
      for ( int i = 0; i < MaxSize / 2 / 1024; i++ )
      {
         fread(Buf, 1024, 1, fin);
         fwrite(Buf, 1024, 1, fout);
      }
      snprintf(Buf, 500, "\n\n%ld bytes skipped\n\n", FileSize - MaxSize);
      fwrite(Buf, strlen(Buf), 1, fout);
      fseek(fin, FileSize - MaxSize / 2, SEEK_SET);
      for ( int i = 0; i < MaxSize / 2 / 1024; i++ )
      {
         fread(Buf, 1024, 1, fin);
         fwrite(Buf, 1024, 1, fout);
      }
      fclose(fin);
      fclose(fout);
   }
   else
   {
      snprintf(FileName1, 500, "%s", FileName);
   }

   if ( FromCodeSet != ToCodeSet &&
        FromCodeSet != csNone && ToCodeSet != csNone )
   {
      ConvFile(FromCodeSet, ToCodeSet, FileName1, FileName2);
      snprintf(FileName1, 500, "tmpfile2_%d_%d", getpid(), ThreadIdx);
      snprintf(FileName2, 500, "tmpfile1_%d_%d", getpid(), ThreadIdx);
   }

   int ErrCode;

   if ( !TextFileCompress(FileName1, FileName2, ErrCode) )
   {
      snprintf(ErrMsg, 2048, "TextFileCompress(%s) fail, errno = %d", FileName1, ErrCode);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto error;
   }

   if ( !WriteFileToSocket(Sock, FileName2) )
   {
      snprintf(ErrMsg, 2048, "WriteFileToSocket fail");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto error;
   }

   goto end;

error :

   Result = false;

end :

   if ( strcmp(FileName1, FileName) != 0 )
   {
      unlink(FileName1);
   }

   if ( strcmp(FileName2, FileName) != 0 )
   {
      unlink(FileName2);
   }

   return Result;
}

static bool ReadFileFromSocket(int Sock, const char *FileName)
{
   int r;
   int FileLength;
   char Buf[1024 + 1];
   char ErrMsg[2048 + 1];

   r = BsReadFromSocket(Sock, Buf, 8, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsReadFromSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   Buf[8] = '\0';
   FileLength = HexStrToInt(Buf);

   int FileFd = open(FileName, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC, 0666);

   if ( FileFd < 0 )
   {
      snprintf(ErrMsg, 2048, "Create %s fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   for ( int i = 0; i < FileLength; )
   {
      int Len = FileLength - i;
      if ( Len > 1024 )
      {
         Len = 1024;
      }
      r = BsReadFromSocket(Sock, Buf, Len, COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsReadFromSocket fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(FileFd);
         return false;
      }
      write(FileFd, Buf, r);
      i += r;
   }

   close(FileFd);
   return true;
}

static bool IsSafeTime(time_t CurrTime, time_t Time)
{
   if ( Time == 0 )
   {
      return true;
   }

   if ( CurrTime < Time - 10 * 60  )
   {
      return true;
   }

   if ( CurrTime > Time + 10 * 60  )
   {
      return true;
   }

   return false;
}

static bool IsSpecialDate(const HString &SpecialDate)
{
   return GetEnv(SpecialDate + "_IND") == "Y";
}

static const char *BatchStatusStrings[] =
{
   "NOT_STARTED",
   "STARTED",
   "PAUSED",
   "STOPPING",
   "STOPPED",
};

static const char *BatchStatusToString(HBatchStatus Status)
{
   if ( Status > bsEND )
   {
      Status = bsNotStarted;
   }

   return BatchStatusStrings[Status];
}

static bool ParseDailyBatchString(const HString &Src,
   HString &BatchName, vector<HHourMin> &TimeVec)
{
   char w[80 + 1];
   int Pos = 0;

   TimeVec.clear();

   for ( ; ; )
   {
      HHourMin HourMin;
      Pos += ReadWord(Src.c_str() + Pos, w, " :", 80);
      if ( w[0] == '\0' )
      {
         BatchName = "";
         return true;
      }
      HourMin.Hour = StrToInt(w);
      if ( HourMin.Hour < 0 || HourMin.Hour >= 24 )
      {
         return false;
      }
      Pos += ReadWord(Src.c_str() + Pos, w, " :", 80);
      if ( strcmp(w, ":" ) != 0 )
      {
         return false;
      }
      Pos += ReadWord(Src.c_str() + Pos, w, " ;,", 80);
      HourMin.Min = StrToInt(w);
      if ( HourMin.Min < 0 || HourMin.Min >= 60 )
      {
         return false;
      }
      TimeVec.push_back(HourMin);
      Pos += ReadWord(Src.c_str() + Pos, w, " ;,", 80);
      if ( strcmp(w, ";" ) == 0 )
      {
         continue;
      }
      if ( strcmp(w, "," ) != 0 )
      {
         return false;
      }
      break;
   }

   SortTimeVec(TimeVec);
   Pos += ReadWord(Src.c_str() + Pos, w, " ", 80);
   BatchName = w;
   return true;
}

static bool ParseBatchAlarmString(const HString &Src,
   HString &BatchName, int &Hour, int &Min, int &EndHour, int &EndMin)
{
   BatchName = "";
   Hour = -1;
   Min = 0;
   EndHour = -1;
   EndMin = 0;

   vector<HString> TokenVec;

   SplitString(Src.c_str(), ',', TokenVec);

   if ( TokenVec.size() == 1 && TokenVec[0] == "" )
   {
      return true;
   }

   if ( TokenVec.size() != 2 && TokenVec.size() != 3 )
   {
      return false;
   }

   if ( TokenVec[0] != "" )
   {
      char w[80 + 1];
      const char *s = TokenVec[0].c_str();
      int Pos = 0;
      Pos += ReadWord(s + Pos, w, " :", 80);
      Hour = StrToInt(w);
      if ( Hour < 0 || Hour >= 24 )
      {
         return false;
      }
      Pos += ReadWord(s + Pos, w, " :", 80);
      if ( strcmp(w, ":" ) != 0 )
      {
         return false;
      }
      Pos += ReadWord(s + Pos, w, " ", 80);
      Min = StrToInt(w);
      if ( Min < 0 || Min >= 60 )
      {
         return false;
      }
   }

   if ( TokenVec.size() == 2 )
   {
      BatchName = TokenVec[1];
   }
   else
   {
      BatchName = TokenVec[2];
      char w[80 + 1];
      const char *s = TokenVec[1].c_str();
      int Pos = 0;
      Pos += ReadWord(s + Pos, w, " :", 80);
      EndHour = StrToInt(w);
      if ( EndHour < 0 || EndHour >= 24 )
      {
         return false;
      }
      Pos += ReadWord(s + Pos, w, " :", 80);
      if ( strcmp(w, ":" ) != 0 )
      {
         return false;
      }
      Pos += ReadWord(s + Pos, w, " ", 80);
      EndMin = StrToInt(w);
      if ( EndMin < 0 || EndMin >= 60 )
      {
         return false;
      }
   }

   return true;
}

static void WriteCtldat()
{
   char ErrMsg[2048 + 1];

   {
      HFileLockWrite Lock(CtldatLockFile.c_str());
      int Fd = open(CtldatFile.c_str(), O_CREAT|O_WRONLY|O_SYNC, 0666);
      if ( Fd < 0 )
      {
         snprintf(ErrMsg, 2048, "open %s fail, errno = %d", CtldatFile.c_str(), errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      if ( write(Fd, &Ctldat, sizeof(Ctldat)) < 0 )
      {
         snprintf(ErrMsg, 2048, "write fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Fd);
         return;
      }
      if ( close(Fd) < 0 )
      {
         snprintf(ErrMsg, 2048, "close fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
   }

   if ( MyRole == nrMaster )
   {
      BsWriteToSocket(SockDb, "C", 1, 0);
   }

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      PushBackupObj(boCtldat);
      DoHotback();
   }

   snprintf(ErrMsg, 2048, "Write control.dat('%s', %d, %d, %d)",
      Ctldat.SeqDate, Ctldat.Seq, Ctldat.ConfigVersion, Ctldat.VarpoolVersion);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
}

static void AddVarpoolVersion()
{
   Ctldat.VarpoolVersion++;
   WriteCtldat();

   for ( int i = 0; i < ClusterNodeVec.size(); i++ )
   {
      HClusterNodeInfo &Node = ClusterNodeVec[i];
      if ( i != MyId )
      {
         Node.ParaJobNum = 0;
      }
   }
}

static void AddConfigVersion()
{
   Ctldat.ConfigVersion++;
   WriteCtldat();
}

static void ExecCmd(int Idx, HExecInput &Input, HString Cmd, HExecOutput &Output)
{
   int i;
   int r;
   int SockUD;
   char ErrMsg[2048 + 1];
   const HString &SocketExecName = SocketExecNameVec[Idx];

   for ( i = 0; i < 40; i++ )
   {
      SockUD = BsCreateClientSocketUD(SocketExecName.c_str(), COMM_TIME_OUT);
      if ( SockUD < 0 && errno == EAGAIN )
      {
         int ErrnoBak = errno;
         if ( ExitFlag )
         {
            errno = ErrnoBak;
            break;
         }
         snprintf(ErrMsg, 2048, "BsCreateClientSocketUD(%s) fail, errno = %d", SocketExecName.c_str(), errno);
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         BsSleep(50);
         continue;
      }
      break;
   }

   if ( i == 40 )
   {
      Output.Rc = -1;
      Output.Errno = errno;
      Output.Pid = -1;
      snprintf(ErrMsg, 2048, "SIMBAT_CHECK : ExecCmd retry %d times fail", i);
      Syslog.Log(__FILE__, __LINE__, llInfo, "ONERR", ErrMsg);
      return;
   }

   if ( SockUD < 0 )
   {
      Output.Rc = -1;
      Output.Errno = errno;
      Output.Pid = -1;
      snprintf(ErrMsg, 2048, "CreateClientSocketUD '%s' fail, errno = %d", SocketExecName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   Input.CommStartTime = time(NULL);
   Input.CmdStrLen = Cmd.length();
   Input.MasterDownFlag = MasterDownFlag;
   r = BsWriteToSocket(SockUD, &Input, sizeof(Input), COMM_TIME_OUT);

   if ( r != 0 )
   {
      Output.Rc = -1;
      Output.Errno = errno;
      Output.Pid = -1;
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(SockUD);
      return;
   }

   r = BsWriteToSocket(SockUD, Cmd.c_str(), Cmd.length(), COMM_TIME_OUT);

   if ( r != 0 )
   {
      Output.Rc = -1;
      Output.Errno = errno;
      Output.Pid = -1;
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(SockUD);
      return;
   }

   r = BsReadFromSocket(SockUD, &Output, sizeof(Output), COMM_TIME_OUT);

   if ( r != sizeof(Output) )
   {
      Output.Rc = -1;
      Output.Errno = errno;
      Output.Pid = -1;
      snprintf(ErrMsg, 2048, "BsReadFromSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(SockUD);
      return;
   }

   close(SockUD);
}

static void DoTrigger(const HString &FullName, const char *Type1,
   const char *Type2, const char *Type3)
{
#ifdef SIMBAT_ENTERPRISE
   DoTrigger_aux(FullName, Type1, Type2, Type3);
#endif
}

static void DoBatchTrigger(const char *Type, bool Sleep)
{
   if ( BatchRestartFlag || BatchTrigger == "" )
   {
      return;
   }

   char Command[8192 + 1];

   snprintf(Command, 8192, "%s%s %s", Sleep ? "sleep 10;" : "",
      BatchTrigger.c_str(), Type);

   HExecInput Input;
   HExecOutput Output;

   memset(&Input, 0, sizeof(Input));
   Input.Func = efTrigger;
   ExecCmd(0, Input, Command, Output);
}

static void DoBatchAlarmTrigger(const char *Type)
{
   char FileName[500 + 1];
   HXmlTree Xml;
   HString SbtFileName;
   char ErrMsg[2048 + 1];

   snprintf(FileName, 500, "%s/bats/%s.sbt", SimbatBase, AlarmBatchName.c_str());
   SbtFileName = FileName;

   if ( !Xml.LoadFromFile(SbtFileName.c_str()) )
   {
      snprintf(ErrMsg, 2048, "Load %s fail, %s", SbtFileName.c_str(), Xml.GetErrorString().c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   HString Trigger = Xml.GetProperty("BATCH_TRIGGER");

   if ( Trigger == "" )
   {
      return;
   }

   char Command[8192 + 1];

   snprintf(Command, 8192, "%s %s", Trigger.c_str(), Type);

   HExecInput Input;
   HExecOutput Output;

   memset(&Input, 0, sizeof(Input));
   Input.Func = efTrigger;
   ExecCmd(0, Input, Command, Output);
}

static void ChangeBatchStatus(HBatchStatus Status)
{
   char ErrMsg[2048 + 1];

   Batch.Status = Status;

   {
      HFileLockWrite Lock("$SIMBAT_BASE/tmp/batch_status.lck");
      char FileName[500 + 1];
      int Fd;
      snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());
      Fd  = open(FileName, O_CREAT|O_WRONLY|O_SYNC, 0666);
      if ( Fd < 0 )
      {
         snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      if ( write(Fd, &Batch, sizeof(HBatch)) < 0 )
      {
         snprintf(ErrMsg, 2048, "write fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Fd);
         return;
      }
      if ( close(Fd) < 0 )
      {
         snprintf(ErrMsg, 2048, "close fail, errno = %d", errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Fd);
         return;
      }
   }

   BsWriteToSocket(SockDb, "B", 1, 0);
   PushBackupObj(boBatchStatus);
   DoHotback();

   int SyncQty;

   HEventLogger::Sync(SyncQty);
}

static void SetBatchStopStatus()
{
   char ErrMsg[2048 + 1];
   char FileName[500 + 1];

   if ( !RebuildSummary )
   {
      BkCurrTime(Batch.EndTime);
      ChangeBatchStatus(bsStopped);
      if ( BatchRestartFlag )
      {
         return;
      }
      DoBatchTrigger("STOP", true);
      JobAlarmMap.clear();
      BatchEndAlarmedFlag = false;
      snprintf(ErrMsg, 2048, "Batch %s stopped", Batch.Name);
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      if ( ParaMonFile != NULL )
      {
         fclose(ParaMonFile);
         ParaMonFile = NULL;
      }
      SaveOutlineToXml(&SbtXml);
      snprintf(FileName, 500, "%s/log_%s/offline.xml", LogDir.c_str(), LastBatchTime);
      if ( !SbtXml.SaveToFile(FileName) )
      {
         snprintf(ErrMsg, 2048, "SaveToFile fail : %s", SbtXml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
   }

   snprintf(FileName, 500, "%s/log_%s/summary.txt", LogDir.c_str(), LastBatchTime);

   FILE *f = fopen(FileName, "at");

   if ( f == NULL )
   {
      return;
   }

   time_t TotalSec = StringToTime(Batch.EndTime) - StringToTime(Batch.StartTime);
   int TotalJobQty = 0;

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
           Job.Type != jtTime && Job.Type != jtBatch )
         continue;
      if ( Job.PartFlag )
      {
         TotalJobQty += Job.PartVec.size();
      }
      else
      {
         TotalJobQty++;
      }
   }

   fprintf(f, "\nBatch end time : %.4s/%.2s/%.2s %.2s:%.2s:%.2s, elapsed : %02d:%02d:%02d, total jobs : %d\n",
      Batch.EndTime, Batch.EndTime + 4, Batch.EndTime + 4 + 2,
      Batch.EndTime + 4 + 2 + 2, Batch.EndTime + 4 + 2 + 2 + 2,
      Batch.EndTime + 4 + 2 + 2 + 2 + 2, TotalSec / 3600, TotalSec % 3600 / 60, TotalSec % 60, TotalJobQty);

   HCritical::WriteCriticalPath(f);

   if ( AppendLogToSummary )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
              Job.Type != jtTime && Job.Type != jtBatch )
            continue;
         vector<HString> PartVec;
         if ( Job.PartFlag )
         {
            for ( int i = 0; i < Job.PartVec.size(); i++ )
            {
               part_info &Part = Job.PartVec[i];
               if ( Part.Status != jsSkipped && Part.Status != jsFail &&
                    Part.RunningTimes <= 1 )
                  continue;
               PartVec.push_back(Job.Name + JobNameDelimiter + PartIdxString(Part.Idx));
            }
         }
         else
         {
            if ( Job.Status != jsSkipped && Job.Status != jsFail &&
                 Job.RunningTimes <= 1 )
               continue;
            PartVec.push_back(Job.Name);
         }
         for ( int i = 0; i < PartVec.size(); i++ )
         {
            snprintf(FileName, 500, "%s/log_%s/%s", LogDir.c_str(), LastBatchTime, PartVec[i].c_str());
            FILE *LogFile = fopen(FileName, "rt");
            if ( LogFile == NULL )
               continue;
            char Line[4096 + 1];
            fprintf(f, "\n==========%s==========\n\n", PartVec[i].c_str());
            for ( ; ReadLine(LogFile, Line, 4096) != -1; )
               fprintf(f, "%s\n", Line);
            fclose(LogFile);
         }
      }
   }

   fclose(f);
}

static void CheckBatchStop()
{
   if ( ( Batch.Status == bsStopping && RunningJobNum == 0 ) ||
        ( Batch.Status != bsNotStarted && Batch.Status != bsStopped && CompletedJobNum == TotalJobNum ) )
   {
      SetBatchStopStatus();
      if ( NextBatchName != "" )
      {
         HString ErrStr;
         StartBatch(NextBatchName.c_str(), ErrStr);
         NextBatchName = "";
      }
      else if ( EndBatchDate != "" )
      {
         HString NextDate = NextBatchDate == "" ? CalcNextScheduleDate(BatchDate) : NextBatchDate;
         if ( NextDate <= EndBatchDate )
         {
            HString ErrStr;
            HString BatchName = Batch.Name;
            StartBatch(BatchName.c_str(), ErrStr, false, NextDate.c_str());
         }
         else
         {
            EndBatchDate = "";
            SetEnv("SIMBAT_END_DATE", EndBatchDate.c_str());
         }
      }
   }

}

static void LoadFromCfg(HXmlTree &Cfg, bool InitFlag)
{
   char ErrMsg[2048 + 1];

   SyslogDir_config = Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc");
   SyslogDir = EnvEvaluate(SyslogDir_config);
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));

   snprintf(ErrMsg, 2048, "LoadFromCfg()");
   Syslog.Log(__FILE__, __LINE__, llDebug, "ENTRY", ErrMsg);

   if ( InitFlag )
   {
      if ( !GetMyId(SimbatBase, MyId, MyRole, ErrMsg) )
      {
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      LocalPort = StrToInt(Cfg.GetValue("PORT").c_str());
      NumOfListener = StrToInt(Cfg.GetValue("NUM_OF_LISTENER", MyRole == nrGateway ? "20" : "5").c_str());
      if ( NumOfListener < 5 )
      {
         NumOfListener = 5;
      }
      NumOfDispatcher = StrToInt(Cfg.GetValue("NUM_OF_DISPATCHER", "5").c_str());
      if ( NumOfDispatcher < 5 )
      {
         NumOfDispatcher = 5;
      }
      NumOfExecutor = StrToInt(Cfg.GetValue("NUM_OF_EXECUTOR", "3").c_str());
      if ( NumOfExecutor < 3 )
      {
         NumOfExecutor = 3;
      }
      StatusInputQueueSize = StrToInt(Cfg.GetValue("STATUS_INPUT_QUEUE_SIZE", "20480").c_str());
      if ( StatusInputQueueSize < 1000 )
      {
         StatusInputQueueSize = 1000;
      }
      DispatchQueueSize = StrToInt(Cfg.GetValue("DISPATCH_QUEUE_SIZE", "20480").c_str());
      if ( DispatchQueueSize < 500 )
      {
         DispatchQueueSize = 500;
      }
      HasAlarmFunc = false;
      if ( MyRole == nrMaster || MyRole == nrGateway )
      {
         HString PluginFileName = EnvEvaluate(Cfg.GetValue("PLUGIN_PUSH_ALARM"));
         if ( PluginFileName != "" )
         {
            void *PluginHandle = dlopen(PluginFileName.c_str(), RTLD_NOW);
            if ( PluginHandle == NULL )
            {
               snprintf(ErrMsg, 2048, "dlopen(%s) fail, error = %s", PluginFileName.c_str(), dlerror());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               AlarmFunc = (SIMBAT_PLUGIN_PUSH_ALARM)dlsym(PluginHandle, "SimbatPluginPushAlarm");
               if ( AlarmFunc == NULL )
               {
                  snprintf(ErrMsg, 2048, "dlsym(%s) fail, error = %s", "SimbatPluginPushAlarm", dlerror());
                  Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               }
               else
               {
                  HasAlarmFunc = true;
                  snprintf(ErrMsg, 2048, "Load plugin(%s) completed", PluginFileName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
               }
            }
         }
      }
      HasOutlineFunc = false;
      if ( MyRole == nrMaster || MyRole == nrGateway )
      {
         HString PluginFileName = EnvEvaluate(Cfg.GetValue("PLUGIN_PUSH_OUTLINE"));
         if ( PluginFileName != "" )
         {
            void *PluginHandle = dlopen(PluginFileName.c_str(), RTLD_NOW);
            if ( PluginHandle == NULL )
            {
               snprintf(ErrMsg, 2048, "dlopen(%s) fail, error = %s", PluginFileName.c_str(), dlerror());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               OutlineFunc = (SIMBAT_PLUGIN_PUSH_OUTLINE)dlsym(PluginHandle, "SimbatPluginPushOutline");
               if ( OutlineFunc == NULL )
               {
                  snprintf(ErrMsg, 2048, "dlsym(%s) fail, error = %s", "SimbatPluginPushOutline", dlerror());
                  Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               }
               else
               {
                  HasOutlineFunc = true;
                  snprintf(ErrMsg, 2048, "Load plugin(%s) completed", PluginFileName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
               }
            }
         }
      }
      HasDbFunc = false;
      if ( MyRole == nrMaster || MyRole == nrGateway )
      {
         HString PluginFileName = EnvEvaluate(Cfg.GetValue("PLUGIN_DB_STATUS"));
         if ( PluginFileName != "" )
         {
            HasDbFunc = true;
            DbFunc.Connect = DbConnect;
            DbFunc.CountStatus = DbCountStatus;
            DbFunc.ClearStatus = DbClearStatus;
            DbFunc.WriteStatus = DbWriteStatus;
            DbFunc.ReadStatus = DbReadStatus;
            DbFunc.RewriteStatus = DbRewriteStatus;
            DbFunc.WriteHis = DbWriteHis;
            DbFunc.ReadStatistic = DbReadStatistic;
            DbFunc.WriteStatistic = DbWriteStatistic;
         }
      }
#ifdef SIMBAT_ENTERPRISE
      if ( MyRole == nrMaster )
      {
         HString Str = Cfg.GetValue("GATEWAY_ADDR");
         if ( Str != "" )
         {
            SplitString(Str.c_str(), ',', GatewayAddrVec, true);
         }
         for ( int i = 0; i < GatewayAddrVec.size(); i++ )
         {
            HString &AddrStr = GatewayAddrVec[i];
            vector<HString> AddrPort;
            SplitString(AddrStr.c_str(), ':', AddrPort, true);
            if ( AddrPort.size() != 2 )
            {
               snprintf(ErrMsg, 2048, "Invalid gateway address(%s)", AddrStr.c_str());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
         }
         if ( !GatewayAddrVec.empty() && !HasAlarmFunc )
         {
            AlarmFunc = GatewayPushAlarm;
            HasAlarmFunc = true;
         }
         if ( !GatewayAddrVec.empty() && !HasOutlineFunc )
         {
            OutlineFunc = GatewayPushOutline;
            HasOutlineFunc = true;
         }
         if ( !GatewayAddrVec.empty() && !HasDbFunc && Cfg.GetValueUpper("UPLOAD_STATUS") == "TRUE" )
         {
            HasDbFunc = true;
            DbFunc.Connect = GatewayDbConnect;
            DbFunc.CountStatus = GatewayDbCountStatus;
            DbFunc.ClearStatus = GatewayDbClearStatus;
            DbFunc.WriteStatus = GatewayDbWriteStatus;
            DbFunc.ReadStatus = GatewayDbReadStatus;
            DbFunc.RewriteStatus = GatewayDbRewriteStatus;
            DbFunc.WriteHis = GatewayDbWriteHis;
            DbFunc.ReadStatistic = GatewayDbReadStatistic;
            DbFunc.WriteStatistic = GatewayDbWriteStatistic;
         }
      }
#endif
      if ( MyRole == nrMaster )
      {
         ScheduleDateSet.clear();
         vector<HString> StrVec;
         Cfg.GetValues("SCHEDULE_DATE", StrVec);
         for ( int i = 0; i < StrVec.size(); i++ )
         {
            HString &Date = StrVec[i];
            if ( !CheckDate(Date.c_str()) )
            {
               snprintf(ErrMsg, 2048, "Invalid schedule date(%s)", Date.c_str());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            ScheduleDateSet.insert(Date);
         }
      }
      if ( MyRole == nrGateway )
      {
         WhiteListMap.clear();
         vector<HString> WhiteVec;
         Cfg.GetValues("WHITE_LIST", WhiteVec);
         for ( int i = 0; i < WhiteVec.size(); i++ )
         {
            HString &White = WhiteVec[i];
            vector<HString> StrVec;
            SplitString(White.c_str(), ',', StrVec, true);
            if ( StrVec.empty() || StrVec[0] == "" )
            {
               continue;
            }
            HString Name = StrVec[0];
            set<HString> IpSet;
            for ( int j = 1; j < StrVec.size(); j++ )
            {
               HString &Ip = StrVec[j];
               if ( Ip == "" )
               {
                  continue;
               }
               IpSet.insert(Ip);
            }
            if ( !IpSet.empty() )
            {
               WhiteListMap[Name] = IpSet;
            }
         }
      }
   }

   snprintf(ErrMsg, 2048, "Version : %s %s, build time : %s, %s",
      VERSION_STRING, EDITION_STRING, __DATE__, __TIME__);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);

   JobNameDelimiter = Cfg.GetValue("JOB_NAME_DELIMITER", ":");
   ClearEventFlag = Cfg.GetValueUpper("CLEAR_EVENT", "TRUE") == "TRUE";
   ClearPartFlag = Cfg.GetValueUpper("CLEAR_PART", "TRUE") == "TRUE";
   ShrDir_config = Cfg.GetValue("SHR_DIR", "$SIMBAT_BASE/shr");
   ShrDir = EnvEvaluate(ShrDir_config);
   LogDir_config = Cfg.GetValue("LOG_DIR", "$SIMBAT_BASE/log");
   LogDir = EnvEvaluate(LogDir_config);
   setenv("SIMBAT_SHR_DIR", ShrDir.c_str(), 1);
   setenv("SIMBAT_LOG_DIR", LogDir.c_str(), 1);
   EnableAutoSkip = Cfg.GetValueUpper("ENABLE_AUTO_SKIP", "FALSE") == "TRUE";

   {
      HString Str = Cfg.GetValueUpper("LOG_LOCATION", "LOCAL");
      if ( Str == "LOCAL" )
      {
         LogLocation = llLocal;
      }
      else
      {
         LogLocation = llNfs;
      }
   }

   NumOfRegion = StrToInt(Cfg.GetValue("NUM_OF_REGION", "1").c_str());
   SystemName = Cfg.GetValue("SYSTEM_NAME");
   AlarmSystemName = Cfg.GetValue("ALARM_SYSTEM_NAME", SystemName);
   SystemDescription = Cfg.GetValue("SYSTEM_DESCRIPTION");

   {
      FsCheckVec.clear();
      vector<HXmlTree*> NodeVec;
      Cfg.GetChildren("FS_CHECK", NodeVec);
      for ( int i = 0; i < NodeVec.size(); i++ )
      {
         HFsCheckInfo FsCheck;
         FsCheck.Path = EnvEvaluate(NodeVec[i]->GetValue("PATH"));
         FsCheck.Size = StrToInt(NodeVec[i]->GetValue("SIZE").c_str());
         if ( FsCheck.Size <= 0 )
         {
            snprintf(ErrMsg, 2048, "config.xml FS_CHECK invalid size %d", FsCheck.Size);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            continue;
         }
         FsCheckVec.push_back(FsCheck);
      }
      HFsCheckInfo FsCheck;
      FsCheck.Path = EnvEvaluate("$SIMBAT_BASE/tmp");
      FsCheck.Size = 10;
      FsCheckVec.push_back(FsCheck);
      FsCheck.Path = ShrDir;
      FsCheck.Size = 50;
      FsCheckVec.push_back(FsCheck);
   }

   SlaveSignonCheckTime = StrToInt(Cfg.GetValue("SLAVE_SIGNON_CHECK_TIME", "30").c_str());
   AppendLogToSummary = Cfg.GetValueUpper("APPEND_LOG_TO_SUMMARY", "FALSE") == "TRUE";
   DownloadAuth = Cfg.GetValueUpper("DOWNLOAD_AUTH", "FALSE") == "TRUE";

   HotbackEnableFlag = false;
   HotbackPausedFlag = false;
   HotbackAddr = "";
   HotbackPort = -1;

#ifdef SIMBAT_ENTERPRISE
   HotbackEnableFlag = ( Cfg.GetValueUpper("HOTBACK_ENABLE", "FALSE") == "TRUE" );
   HotbackPausedFlag = ( Cfg.GetValueUpper("HOTBACK_PAUSED", "FALSE") == "TRUE" );

   HString AddrStr = Cfg.GetValue("HOTBACK_ADDR");

   if ( AddrStr == "" )
   {
      HotbackEnableFlag = false;
   }
   else
   {
      vector<HString> AddrPort;
      SplitString(AddrStr.c_str(), ':', AddrPort, true);
      if ( AddrPort.size() != 2 )
      {
         snprintf(ErrMsg, 2048, "Invalid hot-backup address(%s)", AddrStr.c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
      HotbackAddr = AddrPort[0];
      HotbackPort = StrToInt(AddrPort[1].c_str());
   }

   {
      vector<HXmlTree*> NodeVec;
      Cfg.GetChildren("BACKUP", NodeVec);
      for ( int i = 0; i < NodeVec.size(); i++ )
      {
         HXmlTree *Node = NodeVec[i];
         HBackupInfo_run Backup;
         Backup.Name = Node->GetValueUpper("NAME");
         Backup.EnableFlag = Node->GetValueUpper("ENABLE", "FALSE") == "TRUE";
         Backup.PausedFlag = Node->GetValueUpper("PAUSED", "FALSE") == "TRUE";
         Backup.LogFlag = Node->GetValueUpper("LOG", "TRUE") == "TRUE";
         Node->GetValues("CLUSTER", Backup.AddrVec);
         bool ErrorFlag = false;
         for ( int j = 0; j < Backup.AddrVec.size(); j++ )
         {
            HString &AddrStr = Backup.AddrVec[j];
            vector<HString> AddrPort;
            SplitString(AddrStr.c_str(), ':', AddrPort, true);
            if ( AddrPort.size() != 2 )
            {
               snprintf(ErrMsg, 2048, "Invalid backup address(%s)", AddrStr.c_str());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               ErrorFlag = true;
               break;
            }
            if ( j == 0 )
            {
               Backup.Addr = AddrPort[0];
               Backup.Port = StrToInt(AddrPort[1].c_str());
            }
         }
         if ( ErrorFlag )
         {
            continue;
         }
         if ( InitFlag )
         {
            BackupMap[Backup.Name] = Backup;
            continue;
         }
         map<HString, HBackupInfo_run>::iterator Iter = BackupMap.find(Backup.Name);
         if ( Iter != BackupMap.end() )
         {
            Iter->second.EnableFlag = Backup.EnableFlag;
            Iter->second.PausedFlag = Backup.PausedFlag;
            Iter->second.LogFlag = Backup.LogFlag;
         }
      }
   }
#endif
}

static void LoadFromCfgCust(HXmlTree &Cfg)
{
   char ErrMsg[2048 + 1];
   HString OldDailyBatch = DailyBatch;

   DailyBatch = Cfg.GetValue("DAILY_BATCH");

   if ( DailyBatch != OldDailyBatch )
   {
      HString BatchName;
      vector<HHourMin> TimeVec;
      snprintf(ErrMsg, 2048, "DailyBatch = %s", DailyBatch.c_str());
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      if ( ParseDailyBatchString(DailyBatch, BatchName, TimeVec) )
      {
         DailyBatchName = BatchName;
         DailyBatchTimeVec = TimeVec;
         if ( BatchName == "" )
         {
            NextDailyBatchTime = 0;
         }
         else
         {
            NextDailyBatchTime = DailyBatchTimeVec[0].Time;
            snprintf(ErrMsg, 2048, "NextDailyBatchTime = %s", TimeToString(NextDailyBatchTime).c_str());
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         }
      }
      else
      {
         snprintf(ErrMsg, 2048, "config_cust.xml DAILY_BATCH syntax error");
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
   }

   HString OldBatchAlarm = BatchAlarm;

   BatchAlarm = Cfg.GetValue("BATCH_ALARM");

   if ( BatchAlarm != OldBatchAlarm )
   {
      HString BatchName;
      int Hour;
      int Min;
      int EndHour;
      int EndMin;
      snprintf(ErrMsg, 2048, "BatchAlarm = %s", BatchAlarm.c_str());
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      if ( ParseBatchAlarmString(BatchAlarm, BatchName, Hour, Min, EndHour, EndMin) )
      {
         AlarmBatchName = BatchName;
         BatchAlarmedFlag = false;
         BatchEndAlarmedFlag = false;
         NextBatchAlarmTime = 0;
         NextBatchEndAlarmTime = 0;
         if ( BatchName != "" )
         {
            char w[80 + 1];
            time_t CurrTime = time(NULL);
            AlarmBatchStartTime = 0;
            if ( strcmp(AlarmBatchName.c_str(), Batch.Name) == 0 &&
                 Batch.StartTime[0] != '\0' )
            {
               AlarmBatchStartTime = StringToTime(Batch.StartTime);
            }
            NextBatchAlarmTime = 0;
            NextBatchEndAlarmTime = 0;
            if ( Hour != -1 )
            {
               BkToday(w);
               snprintf(w + 8, 6 + 1, "%02d%02d00", Hour, Min);
               NextBatchAlarmTime = StringToTime(w);
               if ( NextBatchAlarmTime < CurrTime )
               {
                  NextBatchAlarmTime += 24 * 60 * 60;
               }
               snprintf(ErrMsg, 2048, "NextBatchAlarmTime = %s", TimeToString(NextBatchAlarmTime).c_str());
               Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            }
            if ( EndHour != -1 )
            {
               BkToday(w);
               snprintf(w + 8, 6 + 1, "%02d%02d00", EndHour, EndMin);
               NextBatchEndAlarmTime = StringToTime(w);
               if ( NextBatchEndAlarmTime < CurrTime )
               {
                  NextBatchEndAlarmTime += 24 * 60 * 60;
               }
               snprintf(ErrMsg, 2048, "NextBatchEndAlarmTime = %s", TimeToString(NextBatchEndAlarmTime).c_str());
               Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            }
         }
      }
      else
      {
         snprintf(ErrMsg, 2048, "config_cust.xml BATCH_ALARM syntax error");
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
   }

   CodeSet = StrToCodeSet(Cfg.GetValueUpper("CODE_SET", "UTF8"));
   ParaJobNum = StrToInt(Cfg.GetValue("PARA_JOB_NUM", "4").c_str());
   DedicatedFlag = Cfg.GetValueUpper("DEDICATED", "FALSE") == "TRUE";
}

static void LoadFromCfgAuth(HXmlTree &Cfg)
{
   LoginMap.clear();

   {
      vector<HXmlTree*> Nodes;
      map<HString, set<HString> > RoleMap;
      Cfg.GetChildren("ROLE", Nodes);
      for ( int i = 0; i < Nodes.size(); i++ )
      {
         HXmlTree *Role = Nodes[i];
         HString Id = Role->GetValue("ID");
         vector<HString> AuthVec;
         Role->GetValues("AUTH", AuthVec);
         set<HString> AuthSet(AuthVec.begin(), AuthVec.end());
         RoleMap[Id] = AuthSet;
      }
      Cfg.GetChildren("LOGIN", Nodes);
      for ( int i = 0; i < Nodes.size(); i++ )
      {
         HXmlTree *Login = Nodes[i];
         HString EncrypedPassword = Login->GetValue("PASSWORD");
         char Password[32 + 1];
         BkAscToHex(Password, EncrypedPassword.c_str(), 32);
         BkTeaDecrypt(Password, PASS_KEY, 32);
         Password[32] = '\0';
         BkRightTrim(Password);
         HLoginInfo LoginInfo;
         LoginInfo.Id = Login->GetValue("ID");
         LoginInfo.Password = Password;
         vector<HString> RoleVec;
         Login->GetValues("ROLE", RoleVec);
         for ( int j = 0; j < RoleVec.size(); j++ )
         {
            HString RoleId = RoleVec[j];
            if ( RoleMap.find(RoleId) == RoleMap.end() )
            {
               continue;
            }
            set<HString> &TmpSet = RoleMap[RoleId];
            LoginInfo.AuthSet.insert(TmpSet.begin(), TmpSet.end());
         }
         LoginMap[LoginInfo.Id] = LoginInfo;
      }
   }
}

static bool LoadClusterInfo(HXmlTree &Cfg)
{
   vector<HString> StrVec;
   char ErrMsg[2048 + 1];

   Cfg.GetValues("CLUSTER", StrVec);
   ClusterFlag = StrVec.size() > 1;

   if ( ClusterFlag )
   {
      ClusterNodeVec.clear();
      RunningGroupMap.clear();
      RunningGroupMap["MAIN"] = 0;
      int RunningGroupIdx = 1;
      for ( int i = 0; i < StrVec.size(); i++ )
      {
         HString &AddrStr = StrVec[i];
         vector<HString> AddrPort;
         SplitString(AddrStr.c_str(), ':', AddrPort, true);
         if ( AddrPort.size() != 2 && AddrPort.size() != 3 )
         {
            snprintf(ErrMsg, 2048, "Invalid cluster address(%s)", AddrStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return false;
         }
         HClusterNodeInfo Node;
         Node.Id = i;
         Node.Addr = AddrPort[0];
         Node.Port = StrToInt(AddrPort[1].c_str());
         if ( Node.Port <= 2048 )
         {
            snprintf(ErrMsg, 2048, "Port %s too small, must be greater than 2048", AddrStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return false;
         }
         HString RunningGroup = "MAIN";
         if ( i != MyId && AddrPort.size() == 3 )
         {
            RunningGroup = AddrPort[2];
            ToUpper(RunningGroup);
         }
         if ( RunningGroupMap.find(RunningGroup) == RunningGroupMap.end() )
         {
            RunningGroupMap[RunningGroup] = RunningGroupIdx++;
         }
         Node.GroupIdx = RunningGroupMap[RunningGroup];
         if ( Node.Id == MyId )
         {
            Node.ParaJobNum = ParaJobNum / 2;
            if ( Node.ParaJobNum == 0 )
            {
               Node.ParaJobNum = 1;
            }
         }
         else
         {
            Node.ParaJobNum = 0;
         }
         ClusterNodeVec.push_back(Node);
         snprintf(ErrMsg, 2048, "Cluster node %d, Addr = %s, Port = %d",
            Node.Id, Node.Addr.c_str(), Node.Port);
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      }
   }
   else
   {
      ClusterNodeVec.clear();
      RunningGroupMap.clear();
      RunningGroupMap["MAIN"] = 0;
      HClusterNodeInfo Node;
      Node.Id = 0;
      Node.Addr = "127.0.0.1";
      Node.Port = LocalPort;
      Node.GroupIdx = 0;
      Node.ParaJobNum = ParaJobNum;
      ClusterNodeVec.push_back(Node);
      MyId = 0;
   }

   return true;
}

static void LoadVarpool()
{
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "LoadVarpool()");
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);

   int r;
   HXmlTree Xml;
   vector<HXmlTree*> Nodes;

   if ( FileExist(VarpoolFile) )
   {
      HFileLockRead Lock(VarpoolLockFile.c_str());
      if( !Xml.LoadFromFile(VarpoolFile.c_str(), false) )
      {
         snprintf(ErrMsg, 2048, "Load var_pool fail, %s", Xml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
   }

   Xml.GetChildren(NULL, Nodes);

   for ( int i = 0; i < Nodes.size(); i++ )
   {
      HString Name = Nodes[i]->GetTag();
      HString Value = Nodes[i]->GetData();
      if ( setenv(Name.c_str(), Value.c_str(), 1) == -1 )
      {
         snprintf(ErrMsg, 2048, "setenv(%s,%s) fail, errno = %d", Name.c_str(), Value.c_str(), errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         break;
      }
   }

   BatchDate = GetEnv("SIMBAT_DATE");
   NextBatchDate = GetEnv("SIMBAT_NEXT_DATE");
   NextScheduleDate = GetEnv("SIMBAT_NEXT_SCHEDULE_DATE");
   EndBatchDate = GetEnv("SIMBAT_END_DATE");
}

static bool SetEnv(const char *Name, const char *Value, bool HotbackFlag)
{
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "SetEnv(%s,%s)", Name, Value);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);

   if ( setenv(Name, Value, 1) == -1 )
   {
      snprintf(ErrMsg, 2048, "setenv(%s,%s) fail, errno = %d", Name, Value, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   HExecInput ExecIn;
   HExecOutput ExecOut;

   memset(&ExecIn, 0, sizeof(ExecIn));
   ExecIn.Func = efSetEnv;
   snprintf(ExecIn.EnvName, sizeof(ExecIn.EnvName), "%s", Name);
   snprintf(ExecIn.FullName, sizeof(ExecIn.FullName), "%s", Value);

   for ( int i = 0; i < NumOfExecutor; i++ )
   {
      ExecCmd(i, ExecIn, "", ExecOut);
   }

   {
      HFileLockWrite Lock(VarpoolLockFile.c_str());
      HXmlTree Xml;
      if ( FileExist(VarpoolFile) && !Xml.LoadFromFile(VarpoolFile.c_str(), false) )
      {
         snprintf(ErrMsg, 2048, "Load %s fail, %s", VarpoolFile.c_str(), Xml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
      Xml.SetValue(Name, Value);
      if ( !SafeSaveXmlToFile(Syslog, ErrMsg, Xml, VarpoolFile.c_str()) )
      {
         return false;
      }
   }

   if ( HotbackFlag )
   {
      PushBackupObj(boVarpool);
      AddVarpoolVersion();
      BsWriteToSocket(SockDb, "V", 1, 0);
   }

   return true;
}

static int AdjustPriority(const HString &JobName)
{
   job_info &Job = Graph[JobName];

   if ( Job.Priority != 0 )
   {
      return Job.Priority;
   }

   group_info &Group = GroupMap[Job.GroupId];

   if ( Group.Agent == "" )
   {
      return 0;
   }

   Job.Priority = AdjustPriority(Group.Agent);
   return Job.Priority;
}

static int AdjustRunningGroupIdx(const HString &JobName)
{
   job_info &Job = Graph[JobName];

   if ( Job.RunningGroupIdx != -1 )
   {
      return Job.RunningGroupIdx;
   }

   if ( Job.Type != jtJob && Job.Type != jtGroup )
   {
      return 0;
   }

   group_info &Group = GroupMap[Job.GroupId];

   if ( Group.Agent == "" )
   {
      return 0;
   }

   Job.RunningGroupIdx = AdjustRunningGroupIdx(Group.Agent);
   return Job.RunningGroupIdx;
}

static HString AdjustBusinessType(const HString &JobName)
{
   job_info &Job = Graph[JobName];

   if ( Job.BusinessType != "" )
   {
      return Job.BusinessType;
   }

   if ( Job.Type != jtJob && Job.Type != jtGroup )
   {
      return "";
   }

   group_info &Group = GroupMap[Job.GroupId];

   if ( Group.Agent == "" )
   {
      return "";
   }

   Job.BusinessType = AdjustBusinessType(Group.Agent);
   return Job.BusinessType;
}

static HAutoRerunInfo AdjustAutoRerun(const HString &JobName)
{
   job_info &Job = Graph[JobName];

   if ( Job.AutoRerun != 0 || Job.AutoRerunTime != 30 )
   {
      return HAutoRerunInfo(Job.AutoRerun, Job.AutoRerunTime);
   }

   group_info &Group = GroupMap[Job.GroupId];

   if ( Group.Agent == "" )
   {
      return HAutoRerunInfo(AutoRerun, AutoRerunTime);
   }

   HAutoRerunInfo Info = AdjustAutoRerun(Group.Agent);
   Job.AutoRerun = Info.AutoRerun;
   Job.AutoRerunTime = Info.AutoRerunTime;
   return Info;
}

static void BackupSummary(const char *OldBatchTime, const char *NewBatchTime)
{
   char Command[2048 + 1];

   snprintf(Command, 2048, "mkdir -p %s/log_%s;rm -f %s/log;ln -s %s/log_%s %s/log",
      LogDir.c_str(), NewBatchTime, LogDir.c_str(), LogDir.c_str(), NewBatchTime, LogDir.c_str());
   System.Run(Command);

   if ( MyRole == nrMaster && OldBatchTime[0] != '\0' )
   {
      snprintf(Command, 2048, "cp -rf %s/part_bak %s/log_%s",
         ShrDir.c_str(), LogDir.c_str(), OldBatchTime);
      System.Run(Command);
      snprintf(Command, 2048, "cp -rf %s/status %s/log_%s",
         ShrDir.c_str(), LogDir.c_str(), OldBatchTime);
      System.Run(Command);
      snprintf(Command, 2048, "cp -f %s/etc/para_job_num.csv %s/log_%s",
         ShrDir.c_str(), LogDir.c_str(), OldBatchTime);
      System.Run(Command);
      snprintf(Command, 2048, "cp -f %s/log_%s/summary.txt %s/his/summary_%s.txt",
         LogDir.c_str(), OldBatchTime, ShrDir.c_str(), OldBatchTime);
      System.Run(Command);
      snprintf(Command, 2048, "cp -f %s/log_%s/offline.xml %s/his/offline_%s.xml",
         LogDir.c_str(), OldBatchTime, ShrDir.c_str(), OldBatchTime);
      System.Run(Command);
   }
}

static bool AscendAnnounce(const HString &Addr, int Port)
{
   int r;
   int Sock;
   char Buf[200 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "AscendAnnounce(%s, %d)", Addr.c_str(), Port);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
   Sock = BsCreateClientSocket(Addr.c_str(), Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      return true;
   }

   snprintf(Buf, 200, "%sP||", PVER);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return true;
   }

   char Result[200 + 1];

   r = BsReadFromSocket(Sock, Result, 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "AscendAnnounce fail");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return true;
   }

   if ( Result[0] != '0' )
   {
      close(Sock);
      return false;
   }

   close(Sock);
   return true;
}

static void GroupAddJobNum(group_info *GroupPtr, bool IsWaitJob, time_t StatusChangeTime)
{
   group_info &Group = *GroupPtr;
   int OldValue = Group.RunningJobNum - Group.RunningWaitJobNum;

   Group.RunningJobNum++;

   if ( IsWaitJob )
   {
      Group.RunningWaitJobNum++;
   }

   int NewValue = Group.RunningJobNum - Group.RunningWaitJobNum;

   if ( Group.AgentPtr != NULL )
   {
      job_info &Job = *Group.AgentPtr;
      if ( OldValue == 0 && NewValue == 1 )
      {
         if ( Job.IdleTimeBegin > 0 )
         {
            Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
            Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
            Job.IdleTimeBegin = 0;
         }
      }
      GroupAddJobNum(Job.GroupPtr, IsWaitJob, StatusChangeTime);
   }
}

static void GroupSubJobNum(group_info *GroupPtr, bool IsWaitJob, time_t StatusChangeTime)
{
   group_info &Group = *GroupPtr;
   int OldValue = Group.RunningJobNum - Group.RunningWaitJobNum;

   Group.RunningJobNum--;

   if ( IsWaitJob )
   {
      Group.RunningWaitJobNum--;
   }

   int NewValue = Group.RunningJobNum - Group.RunningWaitJobNum;

   if ( Group.AgentPtr != NULL )
   {
      job_info &Job = *Group.AgentPtr;
      if ( OldValue == 1 && NewValue == 0 )
      {
         Job.IdleTimeBegin = StatusChangeTime;
      }
      GroupSubJobNum(Job.GroupPtr, IsWaitJob, StatusChangeTime);
   }
}

static void WriteSummary(const job_info &Job, HJobStatus Status, int PartIdx)
{
   if ( Job.Type == jtExternJob )
   {
      return;
   }

   if ( ( Job.Type == jtGroup || ( PartIdx == -1 && Job.PartFlag ) ) &&
        ( Status == jsFail || Status == jsUnknown ) )
   {
      return;
   }

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/log_%s/summary.txt", LogDir.c_str(), LastBatchTime);

   FILE *f = fopen(FileName, "at");

   if ( f == NULL )
   {
      return;
   }

   HString FullName = Job.Name;
   HString StartTime = TimeToString(Job.StartTime);
   HString EndTime = TimeToString(Job.EndTime);
   HString ServerId = IntToString(Job.ServerId + 1);

   if ( Job.Type == jtGroup )
   {
      FullName = FullName + "(" + Job.Group + ")";
   }

   if ( PartIdx != -1 )
   {
      FullName = FullName + JobNameDelimiter + PartIdxString(PartIdx);
      const part_info &Part = Job.PartVec[PartIdx];
      StartTime = TimeToString(Part.StartTime);
      EndTime = TimeToString(Part.EndTime);
      ServerId = IntToString(Part.ServerId + 1);
   }

   int GbkLen;

   if ( CodeSet == csUtf8 )
   {
      char GbkStr[8192 + 1];
      BkStrNCpy(GbkStr, FullName.c_str(), sizeof(GbkStr) - 1);
      ConvString(CodeSet, csGbk, GbkStr);
      GbkLen = strlen(GbkStr);
   }
   else
   {
      GbkLen = FullName.length();
   }

   fprintf(f, "%s", FullName.c_str());

   if ( GbkLen < 40 )
   {
      fprintf(f, "%*s", 40 - GbkLen, "");
   }

   fprintf(f, "|%.4s/%.2s/%.2s.%.2s:%.2s:%.2s|%.4s/%.2s/%.2s.%.2s:%.2s:%.2s|",
      StartTime.c_str(), StartTime.c_str() + 4, StartTime.c_str() + 4 + 2,
      StartTime.c_str() + 8, StartTime.c_str() + 8 + 2, StartTime.c_str() + 8 + 2 + 2,
      EndTime.c_str(), EndTime.c_str() + 4, EndTime.c_str() + 4 + 2,
      EndTime.c_str() + 8, EndTime.c_str() + 8 + 2,
      EndTime.c_str() + 8 + 2 + 2);

   int Delta = StringToTime(EndTime) - StringToTime(StartTime);

   if ( Job.Type == jtGroup ||
        ( Job.PartFlag && PartIdx == -1 ) )
   {
      Delta = Job.EndTime - Job.BeginningTime - Job.IdleTimeSum;
   }

   fprintf(f, "%02d:%02d:%02d    |%-10s|%-10s|%s\n",
      Delta / 3600, Delta % 3600 / 60, Delta % 60,
      JobStatusToString(Status), ServerId.c_str(), Job.Tag.c_str());
   fclose(f);
}

static void ReturnSuccess(const HInputInfo &Input, const char *Result)
{
   int r;
   char ErrMsg[2048 + 1];

   r = BsWriteToSocket(Input.Fd, "0", 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
   }

   if ( Result == NULL )
   {
      Result = "";
   }

   WriteToSocketHSL(Input.Fd, Result, strlen(Result), COMM_TIME_OUT);
   close(Input.Fd);
}

static void ReturnFail(const HInputInfo &Input, const char *ErrStr)
{
   int r;
   char ErrMsg[2048 + 1];

   r = BsWriteToSocket(Input.Fd, "1", 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
   }

   if ( ErrStr == NULL )
   {
      ErrStr = "";
   }

   WriteToSocketHSL(Input.Fd, ErrStr, strlen(ErrStr), COMM_TIME_OUT);
   close(Input.Fd);
}

static bool CheckWhiteList(const HInputInfo &Input, int SystemNameIdx)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() <= SystemNameIdx )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return false;
   }

   if ( WhiteListMap.empty() )
   {
      return true;
   }

   const HString &Name = Input.ArgVec[SystemNameIdx];
   map<HString, set<HString> >::iterator Iter = WhiteListMap.find(Name);

   if ( Iter == WhiteListMap.end() )
   {
      snprintf(ErrMsg, 2048, "System '%s' not found in white list", Name.c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return false;
   }

   if ( Iter->second.find(Input.IpAddr) == Iter->second.end() )
   {
      snprintf(ErrMsg, 2048, "System '%s, %s' not found in white list", Name.c_str(), Input.IpAddr.c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return false;
   }

   return true;
}

static void SaveOutlineToXml(HXmlTree *Xml)
{
   char TmpStr[500 + 1];
   HString AlarmType;

   Xml->SetValue("VERSION", VERSION_STRING);
   Xml->SetValue("EDITION", EDITION_STRING);
   snprintf(TmpStr, 500, "%d", ParaJobNum);
   Xml->SetProperty("PARA_JOB_NUM", TmpStr);
   Xml->SetProperty("CODE_SET", CodeSetToStr(CodeSet));
   Xml->SetProperty("DAILY_BATCH", DailyBatch);
   Xml->SetProperty("BATCH_ALARM", BatchAlarm);
   Xml->SetProperty("DEDICATED", DedicatedFlag ? "Y" : "");
   Xml->SetProperty("BATCH_DATE", BatchDate.c_str());
   Xml->SetProperty("NEXT_BATCH_DATE", NextBatchDate.c_str());
   Xml->SetProperty("NEXT_SCHEDULE_DATE", NextScheduleDate.c_str());
   Xml->SetProperty("END_BATCH_DATE", EndBatchDate.c_str());
   BkCurrTime(TmpStr);
   Xml->SetProperty("CURRENT_TIME", TmpStr);
   snprintf(TmpStr, 500, "%*s%ld", 8 + 6, Batch.StartTime, time(NULL));
   Xml->SetProperty("REFRESH_TICK", TmpStr);
   Xml->SetProperty("AUTO_SKIP", AutoSkip ? "Y" : "");
   Xml->SetProperty("BANNER", EnvEvaluate(Banner));

   if ( TotalJobNum > 0 )
   {
      snprintf(TmpStr, 500, "%5.2f", (float)CompletedJobNum / TotalJobNum * 100.00);
      Xml->SetProperty("COMPLETED_RATIO", TmpStr);
   }

   if ( Batch.Status == bsNotStarted )
   {
      Xml->SetProperty("BATCH_STATUS", "NOT_STARTED");
   }
   else if ( Batch.Status == bsStopped )
   {
      HString StatusStr = "STOPPED";
      if ( FadingPeriod != "" )
      {
         int Hour;
         int Min;
         memcpy(TmpStr, FadingPeriod.c_str(), 2);
         TmpStr[2] = '\0';
         Hour = StrToInt(TmpStr);
         memcpy(TmpStr, FadingPeriod.c_str() + 3, 2);
         TmpStr[2] = '\0';
         Min = StrToInt(TmpStr);
         if ( time(NULL) - StringToTime(Batch.EndTime) > ( Hour * 60 + Min ) * 60 )
         {
            StatusStr = "NOT_STARTED";
         }
      }
      Xml->SetProperty("BATCH_STATUS", StatusStr.c_str());
      Xml->SetProperty("BATCH_START_TIME", Batch.StartTime);
      Xml->SetProperty("BATCH_END_TIME", Batch.EndTime);
   }
   else if ( Batch.Status == bsPaused )
   {
      Xml->SetProperty("BATCH_STATUS", "PAUSED");
      Xml->SetProperty("BATCH_START_TIME", Batch.StartTime);
      Xml->SetProperty("BATCH_END_TIME", Batch.EndTime);
   }
   else
   {
      int Level = 0;
      HString JobName;
      Xml->SetProperty("BATCH_START_TIME", Batch.StartTime);
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
              Job.Type != jtTime && Job.Type != jtBatch )
            continue;
         if ( Job.Status == jsFail || Job.Status == jsUnknown )
         {
            if ( Level < 3 )
            {
               Level = 3;
               JobName = Job.Name;
            }
         }
         if ( Job.Type == jtEventWait && Job.WaitForOperator &&
              Job.Status == jsRunning )
         {
            if ( Level < 2 )
            {
               Level = 2;
               JobName = Job.Name;
            }
         }
         if ( Job.AlarmedFlag )
         {
            if ( Level < 1 )
            {
               Level = 1;
               if ( Job.AlarmTimeType == attDurationFromTheJobStarting )
               {
                  AlarmType = "JOB_ALARM_END";
               }
               else
               {
                  AlarmType = "JOB_ALARM";
               }
               JobName = Job.Name;
            }
         }
      }
      switch ( Level )
      {
      case 0 :
         Xml->SetProperty("BATCH_STATUS", "STARTED");
         break;
      case 1 :
         Xml->SetProperty("BATCH_STATUS", "STARTED");
         Xml->SetProperty("JOB_NAME", JobName);
         break;
      case 2 :
         Xml->SetProperty("BATCH_STATUS", "JOB_WAIT");
         Xml->SetProperty("JOB_NAME", JobName);
         break;
      case 3 :
         Xml->SetProperty("BATCH_STATUS", "JOB_FAIL");
         Xml->SetProperty("JOB_NAME", JobName);
         break;
      }
   }

   {
      vector<HString> StrVec;
      for ( int i = 0; i < ClusterNodeVec.size(); i++ )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[i];
         char TmpStr[200 + 1];
         snprintf(TmpStr, 200, "%03d   %s:%d", i + 1, Node.Addr.c_str(), Node.Port);
         StrVec.push_back(TmpStr);
      }
      Xml->SetValues("CLUSTER", StrVec);
   }

   if ( AlarmType == "" && BatchEndAlarmedFlag )
   {
      AlarmType = "BATCH_ALARM_END";
      Xml->SetProperty("ALARM_BATCH_NAME", AlarmBatchName);
   }

   if ( AlarmType == "" && BatchAlarmedFlag )
   {
      AlarmType = "BATCH_ALARM_START";
      Xml->SetProperty("ALARM_BATCH_NAME", AlarmBatchName);
   }

   if ( AlarmType == "" && Batch.Status == bsPaused )
   {
      if ( PauseReason == prFsCheck )
      {
         AlarmType = "FS_CHECK";
      }
      else if ( PauseReason == prRestart )
      {
         AlarmType = "RESTART";
      }
      else if ( PauseReason == prHotback )
      {
         AlarmType = "HOTBACK";
      }
   }

   Xml->SetProperty("ALARM_TYPE", AlarmType);
}

static void SaveNewStatusToXml(HXmlTree *Xml, time_t LastRefreshTime)
{
   HXmlTree *LastGroupNode = NULL;
   HString LastGroupId;

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.LastStatusChangeTime < LastRefreshTime )
      {
         continue;
      }
      if ( Job.GroupId != LastGroupId )
      {
         LastGroupNode = new HXmlTree(Xml, "GROUP");
         LastGroupNode->SetProperty("NAME", Job.GroupId);
         LastGroupId = Job.GroupId;
      }
      HXmlTree *Node = new HXmlTree(LastGroupNode, "JOB");
      Node->SetProperty("NAME", HString(strchr(Job.Name.c_str(), JobNameDelimiter[0]) + 1));
      Node->SetProperty("STATUS", Job.Status);
      Node->SetProperty("RESTORE_STATUS", Job.Xml->GetProperty("RESTORE_STATUS"));
      Node->SetProperty("BEGINNING_TIME", Job.Xml->GetProperty("BEGINNING_TIME"));
      Node->SetProperty("START_TIME", Job.Xml->GetProperty("START_TIME"));
      Node->SetProperty("END_TIME", Job.Xml->GetProperty("END_TIME"));
      Node->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
      Node->SetProperty("SERVER_ID", Job.ServerId + 1);
      Node->SetProperty("PID", Job.Pid);
      Node->SetProperty("PART_FLAG", Job.Xml->GetProperty("PART_FLAG"));
      Node->SetProperty("BREAKPOINT", Job.Xml->GetProperty("BREAKPOINT"));
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.LastStatusChangeTime < LastRefreshTime )
         {
            continue;
         }
         HXmlTree *PartXml = Part.Xml;
         HXmlTree *PartNode = new HXmlTree(Node, "PART");
         PartNode->SetProperty("ID", PartIdxString(Part.Idx).c_str());
         PartNode->SetProperty("STATUS", Part.Status);
         PartNode->SetProperty("RESTORE_STATUS", PartXml->GetProperty("RESTORE_STATUS"));
         PartNode->SetProperty("BEGINNING_TIME", PartXml->GetProperty("BEGINNING_TIME"));
         PartNode->SetProperty("START_TIME", PartXml->GetProperty("START_TIME"));
         PartNode->SetProperty("END_TIME", PartXml->GetProperty("END_TIME"));
         PartNode->SetProperty("SERVER_ID", Part.ServerId + 1);
         PartNode->SetProperty("PID", Part.Pid);
      }
   }
}

static bool ConvFileToSocket(int Sock, const char *FileName,
   HCodeSet FromCodeSet, HCodeSet ToCodeSet, int ThreadIdx)
{
   bool Result = true;
   char FileName1[500 + 1];
   char ErrMsg[2048 + 1];

   snprintf(FileName1, 500, "tmpfile1_%d_%d", getpid(), ThreadIdx);

   if ( FromCodeSet != ToCodeSet &&
        FromCodeSet != csNone && ToCodeSet != csNone )
   {
      ConvFile(FromCodeSet, ToCodeSet, FileName, FileName1);
   }
   else
   {
      BkStrNCpy(FileName1, FileName, sizeof(FileName1) - 1);
   }

   if ( !WriteFileToSocket(Sock, FileName1) )
   {
      snprintf(ErrMsg, 2048, "WriteFileToSocket fail");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto error;
   }

   goto end;

error :

   Result = false;

end :

   if ( strcmp(FileName1, FileName) != 0 )
   {
      unlink(FileName1);
   }

   return Result;
}

static char CheckPassword(const HString &Id, const HString &Password,
   const HString &Auth)
{
   GraphMutex.Lock();

   if ( LoginMap.find(Id) == LoginMap.end() )
   {
      GraphMutex.Unlock();
      return '1';
   }

   HLoginInfo &Login = LoginMap[Id];

   if ( Login.Password != Password )
   {
      GraphMutex.Unlock();
      return '1';
   }

   if ( Login.AuthSet.find(Auth) == Login.AuthSet.end() )
   {
      GraphMutex.Unlock();
      return '2';
   }

   GraphMutex.Unlock();
   return '0';
}

static bool RerunJob(job_info &Job, int PartIdx)
{
   if ( Job.Type == jtExternJob )
   {
      return false;
   }

   if ( Job.Type == jtGroup )
   {
      bool Found = false;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
      {
         job_info &Item = Iter->second;
         if ( Item.GroupId != Job.Group )
         {
            continue;
         }
         if ( RerunJob(Item, -1) )
         {
            Found = true;
         }
      }
      return Found;
   }

   if ( Job.PartFlag && PartIdx == -1 )
   {
      bool Found = false;
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( ( Part.Status == jsFail || Part.Status == jsUnknown ) &&
              ( Part.RestoreStatus == rsNull ||
                Part.RestoreStatus == rsRestored ) )
         {
            if ( RerunJob(Job, Part.Idx) )
            {
               Found = true;
            }
         }
      }
      return Found;
   }

   if ( Job.PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( ( Part.Status == jsFail || Part.Status == jsUnknown ) &&
           ( Part.RestoreStatus == rsNull ||
             Part.RestoreStatus == rsRestored ) )
      {
         HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
         ChangeStatus(FullName, jsWaiting, 0, 0, 0);
         return true;
      }
   }
   else
   {
      if ( ( Job.Status == jsFail || Job.Status == jsUnknown ) &&
           ( Job.RestoreStatus == rsNull ||
             Job.RestoreStatus == rsRestored ) )
      {
         HString FullName = Job.Name;
         ChangeStatus(FullName, jsWaiting, 0, 0, 0);
         return true;
      }
   }

   return false;
}

static bool RestartJob(const HString &JobName, const HString &Date, const HString &NextDate)
{
   job_info &Job = Graph[JobName];

   if ( Job.Type == jtExternJob )
   {
      return false;
   }

   if ( Job.Status != jsSuccess && Job.Status != jsSkipped )
   {
      return false;
   }

   if ( Job.Type == jtGroup )
   {
      bool Found = false;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         if ( Iter->second.GroupId == Job.Group )
         {
            if ( RestartJob(Iter->first, Date, NextDate) )
            {
               Found = true;
            }
         }
      }
      return Found;
   }

   if ( Date == "" )
   {
      Job.RestartDate = BatchDate;
   }
   else
   {
      Job.RestartDate = Date;
   }

   if ( NextDate == "" )
   {
      Job.RestartNextDate = CalcNextScheduleDate(Job.RestartDate);
   }
   else
   {
      Job.RestartNextDate = NextDate;
   }

   if ( Job.PartFlag )
   {
      Job.RunningTimes = 0;
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
         ChangeStatus(FullName, jsWaiting, 88888, 0, 0);
      }
      return true;
   }

   HString FullName = Job.Name;
   ChangeStatus(FullName, jsWaiting, 88888, 0, 0);
   return true;
}

static void RestoreJob(job_info &Job, int PartIdx)
{
   HString FullName = Job.Name;
   char ErrMsg[2048 + 1];

   if ( Job.PartFlag )
   {
      FullName = FullName + JobNameDelimiter + PartIdxString(PartIdx);
   }

   HExecInput Input;
   HExecOutput Output;
   char Command[8192 + 1];
   char JobSeq[18 + 1];
   HString PartId;

   if ( PartIdx != -1 )
   {
      PartId = Job.PartVec[PartIdx].Id;
   }

   HJobSeq::GetJobSeq(JobSeq);
   snprintf(Command, 8192, "export SIMBAT_RUNNING_TIMES=1;"
      "export SIMBAT_PART='%s';export SIMBAT_PART_TOTAL=%d;%s",
      ToShellArg(PartId).c_str(), Job.PartVec.size(), Job.RestoreCommand.c_str());
   memset(&Input, 0, sizeof(Input));
   BkStrNCpy(Input.BatchTime, LastBatchTime, sizeof(Input.BatchTime) - 1);
   BkStrNCpy(Input.JobSeq, JobSeq, sizeof(Input.JobSeq) - 1);
   Input.Func = efRestore;
   BkStrNCpy(Input.FullName, FullName.c_str(), sizeof(Input.FullName) - 1);
   Input.SrcServerId = MyId;
   Input.DesServerId = MyId; // restore 作业只能在主机执行
   ExecCmd(0, Input, Command, Output);

   if ( Output.Pid == -1 )
   {
      snprintf(ErrMsg, 2048, "Job %s restore fork fail, errno = %d", FullName.c_str(), Output.Errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   Job.LastStatusChangeTime = time(NULL);

   if ( Job.PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      Part.RestoreStatus = rsRestoring;
      HXmlTree *PartXml = Part.Xml;
      PartXml->SetProperty("RESTORE_STATUS", "RESTORING");
      Part.LastStatusChangeTime = Job.LastStatusChangeTime;
   }
   else
   {
      Job.RestoreStatus = rsRestoring;
      Job.Xml->SetProperty("RESTORE_STATUS", "RESTORING");
   }

   DoTrigger(FullName, "RESTORE");
}

static bool SkipJob(job_info &Job, int PartIdx)
{
   if ( Job.Type == jtExternJob )
   {
      return false;
   }

   if ( Job.Type == jtGroup )
   {
      bool Found = false;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
      {
         job_info &Item = Iter->second;
         if ( Item.GroupId != Job.Group )
         {
            continue;
         }
         if ( SkipJob(Item, -1) )
         {
            Found = true;
         }
      }
      return Found;
   }

   if ( Job.PartFlag && PartIdx == -1 )
   {
      bool Found = false;
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.Status == jsFail || Part.Status == jsUnknown )
         {
            if ( SkipJob(Job, Part.Idx) )
            {
               Found = true;
            }
         }
      }
      return Found;
   }

   if ( Job.PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( Part.Status == jsFail || Part.Status == jsUnknown )
      {
         HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
         ChangeStatus(FullName, jsSkipped, 0, 0, 0);
         return true;
      }
   }
   else
   {
      if ( Job.Status == jsFail || Job.Status == jsUnknown )
      {
         HString FullName = Job.Name;
         ChangeStatus(FullName, jsSkipped, 0, 0, 0);
         return true;
      }
   }

   return false;
}

static void KillOneJob(const HString FullName, pid_t Pid, int ServerId, time_t JobStartTime)
{
   char ErrMsg[2048 + 1];

   if ( ServerId == MyId )
   {
      if ( kill(-Pid, SIGINT) == -1 )
      {
         if ( errno == ESRCH )
         {
            snprintf(ErrMsg, 2048, "Job(%s) process not found, status changed to UNKNOWN",
               FullName.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            ChangeStatus(FullName, jsUnknown, 0, 0, 0);
         }
         else
         {
            snprintf(ErrMsg, 2048, "Job(%s) kill(%d, %d) fail, errno = %d",
               FullName.c_str(), -Pid, SIGINT, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
      return;
   }

   HPostFuncInfo PostFuncInfo;

   PostFuncInfo.Func = fcKillProcess;
   PostFuncInfo.FullName = FullName;
   PostFuncInfo.ServerId = ServerId;
   PostFuncInfo.JobStartTime = JobStartTime;
   PostFuncQueue.MasterPushBackNoWait(PostFuncInfo);
}

static void KillJob(const HString &FullName)
{
   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(FullName, JobName, PartIdx);
   job_info &Job = Graph[JobName];
   time_t CurrTime = time(NULL);

   if ( Job.PartFlag )
   {
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PartIdx];
         if ( Part.Status == jsRunning )
         {
            if ( CurrTime - Part.StartTime < 3 )
            {
               return;
            }
            if ( Part.Pid != 0 )
            {
               KillOneJob(FullName, Part.Pid, Part.ServerId, Part.StartTime);
            }
            else
            {
               ChangeStatus(FullName, jsUnknown, 0, 0, 0);
            }
         }
      }
      else
      {
         for ( int i = 0; i < Job.PartVec.size(); i++ )
         {
            part_info &Part = Job.PartVec[i];
            if ( Part.Status == jsRunning )
            {
               if ( CurrTime - Part.StartTime < 3 )
               {
                  continue;
               }
               if ( Part.Pid != 0 )
               {
                  KillOneJob(Job.Name + JobNameDelimiter + PartIdxString(Part.Idx), Part.Pid, Part.ServerId, Part.StartTime);
               }
               else
               {
                  ChangeStatus(Job.Name + JobNameDelimiter + PartIdxString(Part.Idx), jsUnknown, 0, 0, 0);
               }
            }
         }
      }
   }
   else
   {
      if ( Job.Status == jsRunning )
      {
         if ( CurrTime - Job.StartTime < 3 )
         {
            return;
         }
         if ( Job.Pid != 0 )
         {
            KillOneJob(Job.Name, Job.Pid, Job.ServerId, Job.StartTime);
         }
         else
         {
            ChangeStatus(Job.Name, jsUnknown, 0, 0, 0);
         }
      }
   }
}

static bool StopJob(job_info &Job, int PartIdx)
{
   if ( Job.Type == jtExternJob )
   {
      return false;
   }

   if ( Job.Type == jtGroup )
   {
      bool Found = false;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
      {
         job_info &Item = Iter->second;
         if ( Item.GroupId != Job.Group )
         {
            continue;
         }
         if ( StopJob(Item, -1) )
         {
            Found = true;
         }
      }
      return Found;
   }

   if ( Job.PartFlag && PartIdx == -1 )
   {
      bool Found = false;
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.Status == jsRunning )
         {
            if ( StopJob(Job, Part.Idx) )
            {
               Found = true;
            }
         }
      }
      return Found;
   }

   time_t CurrTime = time(NULL);

   if ( Job.PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( Part.Status == jsRunning )
      {
         HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
         Part.LastUserStopTime = CurrTime;
         KillJob(FullName);
         return true;
      }
   }
   else
   {
      if ( Job.Status == jsRunning )
      {
         HString FullName = Job.Name;
         Job.LastUserStopTime = CurrTime;
         KillJob(FullName);
         return true;
      }
   }

   return false;
}

static bool SlaveGetVarpool(const char *Addr, int Port)
{
   int r;
   int Sock;
   char Buf[200 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "SlaveGetVarpool(%s, %d)", Addr, Port);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   Sock = BsCreateClientSocket(Addr, Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      return false;
   }

   snprintf(Buf, 200, "%sU|||var_pool|N", PVER);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   if ( !ReadFileFromSocket(Sock, VarpoolFile.c_str()) )
   {
      return false;
   }

   close(Sock);
   LoadVarpool();

   HExecInput ExecIn;
   HExecOutput ExecOut;

   memset(&ExecIn, 0, sizeof(ExecIn));
   ExecIn.Func = efRefreshVarpool;

   for ( int i = 0; i < NumOfExecutor; i++ )
   {
      ExecCmd(i, ExecIn, "", ExecOut);
   }

   Sock = BsCreateClientSocket(Addr, Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      return false;
   }

   snprintf(Buf, 200, "%sU|||http_post.ini|N", PVER);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/http_post.ini", SimbatBase);

   if ( !ReadFileFromSocket(Sock, FileName) )
   {
      return false;
   }

   close(Sock);
   return true;
}

static bool SlaveGetConfig(const char *Addr, int Port)
{
   int r;
   int Sock;
   char Buf[200 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "SlaveGetConfig(%s, %d)", Addr, Port);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   Sock = BsCreateClientSocket(Addr, Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      return false;
   }

   snprintf(Buf, 200, "%sU|||config.xml|N", PVER);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   if ( !ReadFileFromSocket(Sock, ConfigFile.c_str()) )
   {
      return false;
   }

   close(Sock);

   HXmlTree Cfg;
   HXmlTree CfgCust;

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
   }

   int LocalPortBak = LocalPort;

   LoadFromCfg(Cfg, true);
   LocalPort = LocalPortBak;
   LoadClusterInfo(Cfg);

   {
      char TmpStr[500 + 1];
      snprintf(TmpStr, 500, "%d", LocalPort);
      Cfg.SetValue("PORT", TmpStr);
      Cfg.SetValue("SYSLOG_DIR", SyslogDir_config);
      Cfg.SetValue("SHR_DIR", ShrDir_config);
      Cfg.SetValue("LOG_DIR", LogDir_config);
   }

   Cfg.SaveToFile(ConfigFile.c_str());

   HExecInput ExecIn;
   HExecOutput ExecOut;

   memset(&ExecIn, 0, sizeof(ExecIn));
   ExecIn.Func = efReloadConfig;

   for ( int i = 0; i < NumOfExecutor; i++ )
   {
      ExecCmd(i, ExecIn, "", ExecOut);
   }

   return true;
}

static bool DownloadAuthFile(const char *Addr, int Port)
{
   int r;
   int Sock;
   char Buf[200 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "DownloadAuthFile(%s, %d)", Addr, Port);
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   Sock = BsCreateClientSocket(Addr, Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      return false;
   }

   snprintf(Buf, 200, "%sU|||auth.xml|N", PVER);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   HString AuthFile = ShrDir + "/etc/auth.xml";

   if ( !ReadFileFromSocket(Sock, AuthFile.c_str()) )
   {
      return false;
   }

   close(Sock);

   HXmlTree CfgAuth;

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !CfgAuth.LoadFromFile(AuthFile.c_str()) )
      {
         snprintf(ErrMsg, 2048, "Load auth.xml fail, %s", CfgAuth.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
   }

   LoadFromCfgAuth(CfgAuth);
   return true;
}

static void SlaveSignon(bool &NeedRetry)
{
   int MasterId = 0;
   char FileName[500 + 1];
   char ErrMsg[2048 + 1];

   NeedRetry = false;
   HClusterNodeInfo &Node = ClusterNodeVec[MasterId];
   HString MasterAddr = Node.Addr;
   int MasterPort = Node.Port;
   int r;
   int Sock;
   char Buf[200 + 1];
   time_t BeginTime = time(NULL);

   if ( MasterDownFlag )
   {
      snprintf(ErrMsg, 2048, "SlaveSignon(%s:%d)", HotbackAddr.c_str(), HotbackPort);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      Sock = BsCreateClientSocket(HotbackAddr.c_str(), HotbackPort, COMM_TIME_OUT);
   }
   else
   {
      snprintf(ErrMsg, 2048, "SlaveSignon(%s:%d)", MasterAddr.c_str(), MasterPort);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      Sock = BsCreateClientSocket(MasterAddr.c_str(), MasterPort, COMM_TIME_OUT);
   }

   if ( Sock < 0 )
   {
      goto error;
   }

   snprintf(Buf, 200, "%sQ|||%d|%d|%ld", PVER, MyId, Ctldat.VarpoolVersion, ServerStartTime + MasterTimeDiff);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   r = BsReadFromSocket(Sock, Buf, 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsReadFromSocket error, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   if ( Buf[0] != '0' )
   {
      close(Sock);
      goto error;
   }

   r = ReadFromSocketHSL(Sock, Buf, 200, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   Buf[r] = '\0';
   close(Sock);
   goto normal;

error :

   MasterDownFlag = !MasterDownFlag;
   return;

normal :

   time_t EndTime = time(NULL);
   vector<HString> StrVec;

   SplitString(Buf, '|', StrVec, true);

   time_t MasterTime = StrToLong(StrVec[1].c_str());

   if ( MasterTimeDiffError == -1 || EndTime - BeginTime <= 5 )
   {
      MasterTimeDiffError = ( EndTime - BeginTime ) / 2;
      MasterTimeDiff = MasterTime - BeginTime - MasterTimeDiffError;
      snprintf(ErrMsg, 2048, "MasterTimeDiff = %d, MasterTimeDiffError = %d",
         MasterTimeDiff, MasterTimeDiffError);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   }

   int MasterVarpoolVersion = StrToInt(StrVec[0].c_str());

   if ( Ctldat.VarpoolVersion != MasterVarpoolVersion )
   {
      if ( MasterDownFlag )
      {
         if ( SlaveGetVarpool(HotbackAddr.c_str(), HotbackPort) )
         {
            Ctldat.VarpoolVersion = MasterVarpoolVersion;
            NeedRetry = true;
         }
      }
      else
      {
         if ( SlaveGetVarpool(MasterAddr.c_str(), MasterPort) )
         {
            Ctldat.VarpoolVersion = MasterVarpoolVersion;
            NeedRetry = true;
         }
      }
   }

   int MasterConfigVersion = StrToInt(StrVec[2].c_str());

   if ( Ctldat.ConfigVersion != MasterConfigVersion )
   {
      if ( MasterDownFlag )
      {
         if ( SlaveGetConfig(HotbackAddr.c_str(), HotbackPort) )
         {
            Ctldat.ConfigVersion = MasterConfigVersion;
            NeedRetry = true;
            MasterDownFlag = false;
         }
      }
      else
      {
         if ( SlaveGetConfig(MasterAddr.c_str(), MasterPort) )
         {
            Ctldat.ConfigVersion = MasterConfigVersion;
            NeedRetry = true;
         }
      }
   }

   if ( StrVec[3] != "" && strcmp(LastBatchTime, StrVec[3].c_str()) != 0 )
   {
      if ( LogLocation == llLocal )
      {
         BackupSummary(LastBatchTime, StrVec[3].c_str());
      }
      if ( ClearPartFlag )
      {
         char TmpStr[500 + 1];
         snprintf(TmpStr, 500, "cd %s/part; rm -f * 2> /dev/null", ShrDir.c_str());
         System.Run(TmpStr);
      }
      BkStrNCpy(LastBatchTime, StrVec[3].c_str(), sizeof(LastBatchTime) - 1);
      snprintf(FileName, 500, "%s/etc/last_batch_time", SimbatBase);
      FILE *f = fopen(FileName, "wt");
      if ( f != NULL )
      {
         fprintf(f, "%s\n", LastBatchTime);
         fclose(f);
      }
      GraphMutex.Lock();
      RunningJobMap.clear();
      GraphMutex.Unlock();
   }
}

static void TouchGateway()
{
   int MasterId = 0;
   char FileName[500 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "TouchGateway()");
   Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);

   HString &AddrStr = GatewayAddrVec[GatewayIdx];
   vector<HString> StrVec;

   SplitString(AddrStr.c_str(), ':', StrVec, true);

   HString Addr = StrVec[0];
   int Port = StrToInt(StrVec[1].c_str());
   int r;
   int Sock;
   char Buf[200 + 1];

   Sock = BsCreateClientSocket(Addr.c_str(), Port, COMM_TIME_OUT);

   if ( Sock < 0 )
   {
      goto error;
   }

   snprintf(Buf, 200, "%sR|||%s", PVER, SystemName.c_str());
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   r = BsReadFromSocket(Sock, Buf, 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsReadFromSocket error, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   if ( Buf[0] != '0' )
   {
      close(Sock);
      goto error;
   }

   r = ReadFromSocketHSL(Sock, Buf, 200, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      goto error;
   }

   Buf[r] = '\0';
   close(Sock);
   goto normal;

error :

   if ( GatewayAddrVec.size() > 1 )
   {
      GatewayIdx = 1 - GatewayIdx;
   }

   return;

normal :

   int MasterAuthVersion = StrToInt(Buf);

   if ( AuthVersion != MasterAuthVersion )
   {
      if ( DownloadAuthFile(Addr.c_str(), Port) )
      {
         AuthVersion = MasterAuthVersion;
      }
   }
}

static bool operator<(const HSortJob &lhs, const HSortJob &rhs)
{
   if ( lhs.Priority > rhs.Priority )
   {
      return true;
   }

   if ( lhs.Priority < rhs.Priority )
   {
      return false;
   }

   if ( lhs.PartitionStartTimeTick != 0 &&
        rhs.PartitionStartTimeTick == 0 )
   {
      return true;
   }

   if ( lhs.PartitionStartTimeTick == 0 &&
        rhs.PartitionStartTimeTick != 0 )
   {
      return false;
   }

   if ( lhs.PartitionStartTimeTick < rhs.PartitionStartTimeTick )
   {
      return true;
   }

   if ( lhs.PartitionStartTimeTick > rhs.PartitionStartTimeTick )
   {
      return false;
   }

   if ( lhs.ScheduleListSeq < rhs.ScheduleListSeq )
   {
      return true;
   }

   if ( lhs.ScheduleListSeq > rhs.ScheduleListSeq )
   {
      return false;
   }

   return lhs.Random < rhs.Random;
}

static bool operator<(const HHourMin &lhs, const HHourMin &rhs)
{
   return lhs.Time < rhs.Time;
}

static void SortTimeVec(vector<HHourMin> &TimeVec)
{
   char w[80 + 1];
   time_t CurrTime = time(NULL);

   BkToday(w);

   for ( int i = 0; i < TimeVec.size(); i++ )
   {
      HHourMin &HourMin = TimeVec[i];
      snprintf(w + 8, 6 + 1, "%02d%02d00", HourMin.Hour, HourMin.Min);
      HourMin.Time = StringToTime(w);
      if ( HourMin.Time <= CurrTime )
      {
         HourMin.Time += 24 * 60 * 60;
      }
   }

   sort(TimeVec.begin(), TimeVec.end());
}

static void RunVec(vector<HPreparedJob> &JobVec)
{
   SET_ACTION_POINT(RunVec, 0x0001);

   char ErrMsg[2048 + 1];

   for ( map<HString, group_info>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      group_info &Group = Iter->second;
      Group.PreparedJobNum = 0;
   }

   for ( map<HString, HBusinessTrafficControl>::iterator Iter = BusinessTrafficControlMap.begin();
         Iter != BusinessTrafficControlMap.end(); Iter++ )
   {
      HBusinessTrafficControl &Control = Iter->second;
      Control.PreparedJobNum = 0;
   }

   SET_ACTION_POINT(RunVec, 0x0002);

   for ( int i = 0; i < JobVec.size(); i++ )
   {
      SET_ACTION_POINT(RunVec, 0x0003);
      HPreparedJob &PreparedJob = JobVec[i];
      job_info &Job = *PreparedJob.JobPtr;
      bool PartFlag = PreparedJob.PartFlag;
      if ( !Job.Anchor )
      {
         continue;
      }
      SET_ACTION_POINT(RunVec, 0x0004);
      PreparedJob.DesServerId = MyId;
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PreparedJob.PartIdx];
         Part.PreparedFlag = false;
      }
      else
      {
         Job.PreparedFlag = false;
      }
      Job.NumOfPreparedPart = 0;
      int RegionId = 0;
      int MinRegionJobNum = -1;
      {
         HClusterNodeInfo &Node = ClusterNodeVec[MyId];
         for ( int i = 0; i < Node.RegionJobNumVec.size(); i++ )
         {
            int JobNum = Node.RegionJobNumVec[i];
            if ( MinRegionJobNum == -1 || JobNum < MinRegionJobNum )
            {
               RegionId = i;
               MinRegionJobNum = JobNum;
            }
         }
      }
      PreparedJob.RegionId = RegionId;
      SET_ACTION_POINT(RunVec, 0x0005);
      ChangeStatus(PreparedJob.FullName, jsRunning, PreparedJob.DesServerId, RegionId, 0);
      SET_ACTION_POINT(RunVec, 0x0006);
   }

   SET_ACTION_POINT(RunVec, 0x0007);

   for ( int i = 0; i < JobVec.size(); i++ )
   {
      SET_ACTION_POINT(RunVec, 0x0008);
      HPreparedJob &PreparedJob = JobVec[i];
      job_info &Job = *PreparedJob.JobPtr;
      bool PartFlag = PreparedJob.PartFlag;
      if ( Job.Anchor )
      {
         continue;
      }
      int ServerId = -1;
      int ServerUsedQty = -1;
      SET_ACTION_POINT(RunVec, 0x0009);
      for ( int i = 0; i < ClusterNodeVec.size(); i++ )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[i];
         if ( DedicatedFlag && ClusterNodeVec.size() > 1 && i == MyId )
         {
            continue;
         }
         if ( Job.RunningGroupIdx != Node.GroupIdx )
         {
            continue;
         }
         int UsedQty = Node.RunningJobNum - Node.RunningWaitJobNum;
         if ( UsedQty >= Node.ParaJobNum )
         {
            continue;
         }
         if ( ServerUsedQty == -1 || UsedQty < ServerUsedQty )
         {
            ServerId = i;
            ServerUsedQty = UsedQty;
         }
      }
      SET_ACTION_POINT(RunVec, 0x000a);
      if ( ServerId == -1 )
      {
         continue;
      }
      int OldServerId = -1;
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PreparedJob.PartIdx];
         OldServerId = Part.ServerId;
      }
      else
      {
         OldServerId = Job.ServerId;
      }
      if ( OldServerId != -1 && ( OldServerId == MyId || ClusterNodeVec[OldServerId].SignonFlag ) )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[OldServerId];
         int UsedQty = Node.RunningJobNum - Node.RunningWaitJobNum;
         if ( UsedQty >= Node.ParaJobNum )
         {
            continue;
         }
         ServerId = OldServerId;
      }
      PreparedJob.DesServerId = ServerId;
      SET_ACTION_POINT(RunVec, 0x000b);
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PreparedJob.PartIdx];
         Part.PreparedFlag = false;
      }
      else
      {
         Job.PreparedFlag = false;
      }
      Job.NumOfPreparedPart = 0;
      int RegionId = 0;
      int MinRegionJobNum = -1;
      {
         HClusterNodeInfo &Node = ClusterNodeVec[ServerId];
         for ( int i = 0; i < Node.RegionJobNumVec.size(); i++ )
         {
            int JobNum = Node.RegionJobNumVec[i];
            if ( MinRegionJobNum == -1 || JobNum < MinRegionJobNum )
            {
               RegionId = i;
               MinRegionJobNum = JobNum;
            }
         }
      }
      PreparedJob.RegionId = RegionId;
      SET_ACTION_POINT(RunVec, 0x000c);
      ChangeStatus(PreparedJob.FullName, jsRunning, PreparedJob.DesServerId, RegionId, 0);
      SET_ACTION_POINT(RunVec, 0x000d);
   }

   int SyncQty;

   SET_ACTION_POINT(RunVec, 0x000e);

   if ( !HEventLogger::Sync(SyncQty) )
   {
      if ( Batch.Status == bsStarted )
      {
         PauseReason = prFsCheck;
         Batch.Status = bsPaused;
         DoBatchTrigger("PAUSE");
         snprintf(ErrMsg, 2048, "File system '%s/status' full", ShrDir.c_str());
         PushAlarmObj("FS_FULL", ErrMsg);
      }
      for ( int i = 0; i < JobVec.size(); i++ )
      {
         HPreparedJob &PreparedJob = JobVec[i];
         ChangeStatus(PreparedJob.FullName, jsWaiting, 0, 0, 0);
      }
      return;
   }

   SET_ACTION_POINT(RunVec, 0x000f);

   for ( int i = 0; i < JobVec.size(); i++ )
   {
      SET_ACTION_POINT(RunVec, 0x0010);
      HPreparedJob &PreparedJob = JobVec[i];
      job_info &Job = *PreparedJob.JobPtr;
      bool PartFlag = PreparedJob.PartFlag;
      HExecInput Input;
      HExecOutput Output;
      char Command[8192 + 1];
      char JobSeq[18 + 1];
      bool AutoRerun = false;
      int RunningTimes = 0;
      HString PartId;
      time_t JobStartTime = 0;
      SET_ACTION_POINT(RunVec, 0x0011);
      if ( PreparedJob.DesServerId != MyId )
      {
         continue;
      }
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PreparedJob.PartIdx];
         if ( Part.PreparedFlag )
         {
            Part.PreparedFlag = false;
            continue;
         }
         RunningTimes = Part.RunningTimes;
         PartId = Part.Id;
         JobStartTime = Part.StartTime;
      }
      else
      {
         if ( Job.PreparedFlag )
         {
            Job.PreparedFlag = false;
            continue;
         }
         RunningTimes = Job.RunningTimes;
         PartId = "";
         JobStartTime = Job.StartTime;
      }
      SET_ACTION_POINT(RunVec, 0x0012);
      if ( Job.AutoRerun > 0 )
      {
         if ( RunningTimes > 0 && RunningTimes <= Job.AutoRerun )
         {
            AutoRerun = true;
         }
      }
      if ( Job.AutoRerun > 0 )
      {
         if ( RunningTimes > 0 && RunningTimes <= Job.AutoRerun )
         {
            AutoRerun = true;
         }
      }
      if ( Job.LoopInterval == 0 )
      {
         HJobSeq::GetJobSeq(JobSeq);
      }
      else
      {
         int MaxCount = Job.LoopCount;
         if ( Job.LoopEndTime > 0 && Job.StartTime > 0 && Job.LoopEndTime > Job.StartTime )
         {
            MaxCount = ( Job.LoopEndTime - Job.LoopStartTime ) / Job.LoopInterval + 1;
            if ( MaxCount > Job.LoopCount )
            {
               MaxCount = Job.LoopCount;
            }
         }
         HJobSeq::GetJobSeq(JobSeq, MaxCount);
      }
      SET_ACTION_POINT(RunVec, 0x0013);
      snprintf(Command, 8192, "export SIMBAT_RUNNING_TIMES=%d;"
         "export SIMBAT_PART='%s';export SIMBAT_PART_TOTAL=%d;export SIMBAT_REGION_ID=%d",
         RunningTimes + 1, ToShellArg(PartId).c_str(), Job.PartVec.size(), PreparedJob.RegionId + 1);
      int CommandLen = strlen(Command);
      if ( Job.RestartFlag )
      {
         snprintf(Command + CommandLen, 8192 - CommandLen, ";export SIMBAT_DATE=%s;export SIMBAT_NEXT_DATE=%s",
            Job.RestartDate.c_str(), Job.RestartNextDate.c_str());
         CommandLen = strlen(Command);
      }
      if ( AutoRerun )
      {
         snprintf(Command + CommandLen, 8192 - CommandLen, ";sleep %d", Job.AutoRerunTime);
         CommandLen = strlen(Command);
      }
      SET_ACTION_POINT(RunVec, 0x0014);
      snprintf(Command + CommandLen, 8192 - CommandLen, ";%s", Job.Command.c_str());
      if ( Job.Type == jtBatch )
      {
         CommandLen = strlen(Command);
         snprintf(Command + CommandLen, 8192 - CommandLen, " %d %s", Job.Step, PreparedJob.FullName.c_str());
      }
      memset(&Input, 0, sizeof(HExecInput));
      BkStrNCpy(Input.BatchTime, LastBatchTime, sizeof(Input.BatchTime) - 1);
      BkStrNCpy(Input.JobSeq, JobSeq, sizeof(Input.JobSeq) - 1);
      Input.Func = efJob;
      BkStrNCpy(Input.FullName, PreparedJob.FullName.c_str(), sizeof(Input.FullName) - 1);
      Input.SrcServerId = MyId;
      Input.DesServerId = PreparedJob.DesServerId;
      Input.JobStartTime = JobStartTime;
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PreparedJob.PartIdx];
         Input.RerunFlag = ( Part.RunningTimes > 0 );
         Input.RestartFlag = ( Part.RunningTimes == 0 && Part.RestartFlag );
      }
      else
      {
         Input.RerunFlag = ( Job.RunningTimes > 0 );
         Input.RestartFlag = ( Job.RunningTimes == 0 && Job.RestartFlag );
      }
      SET_ACTION_POINT(RunVec, 0x0015);
      if ( PartFlag && PartitionDitherCount > 0 )
      {
         Input.DitherMs = PartitionDitherStep * ( PreparedJob.PartIdx % PartitionDitherCount );
      }
      Input.LoopStartTime = Job.LoopStartTime;
      Input.LoopEndTime = Job.LoopEndTime;
      Input.LoopInterval = Job.LoopInterval;
      Input.LoopCount = Job.LoopCount;
      SET_ACTION_POINT(RunVec, 0x0016);
      ExecCmd(i % NumOfExecutor, Input, Command, Output);
      SET_ACTION_POINT(RunVec, 0x0017);
      bool Rerun = ( RunningTimes > 0 );
      if ( Output.Pid == -1 )
      {
         snprintf(ErrMsg, 2048, "Job %s fork fail, errno = %d", PreparedJob.FullName.c_str(), Output.Errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         SET_ACTION_POINT(RunVec, 0x0018);
         ChangeStatus(PreparedJob.FullName, jsFail, 0, 0, 0);
         SET_ACTION_POINT(RunVec, 0x0019);
         if ( AutoSkip )
         {
            snprintf(ErrMsg, 2048, "Auto skip %s", PreparedJob.FullName.c_str());
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            ChangeStatus(PreparedJob.FullName, jsSkipped, 0, 0, 0);
            DoTrigger(PreparedJob.FullName, Rerun ? "RERUN" : "START", "FAIL", "SKIP");
         }
         else
         {
            DoTrigger(PreparedJob.FullName, Rerun ? "RERUN" : "START", "FAIL");
            PushAlarmObj("JOB_FAIL", ErrMsg, Job);
         }
         SET_ACTION_POINT(RunVec, 0x001a);
      }
      else
      {
         SET_ACTION_POINT(RunVec, 0x001b);
         ChangeStatus(PreparedJob.FullName, jsRunning, 0, 0, Output.Pid);
         SET_ACTION_POINT(RunVec, 0x001c);
      }
   }

   SET_ACTION_POINT(RunVec, 0x001d);

   for ( int i = 0; i < JobVec.size(); )
   {
      SET_ACTION_POINT(RunVec, 0x001e);
      int Header = 0;
      int Qty = 0;
      int DispatchIdx = 0;
      DispatchQueue.GetCurrForWrite(Header, Qty);
      SET_ACTION_POINT(RunVec, 0x001f);
      for ( ; i < JobVec.size() && DispatchIdx < Qty; i++ )
      {
         HPreparedJob &PreparedJob = JobVec[i];
         if ( PreparedJob.DesServerId == MyId )
         {
            continue;
         }
         job_info &Job = *PreparedJob.JobPtr;
         bool PartFlag = PreparedJob.PartFlag;
         HExecInput Input;
         bool AutoRerun = false;
         int RunningTimes = 0;
         HString PartId;
         time_t JobStartTime = 0;
         if ( PartFlag )
         {
            part_info &Part = Job.PartVec[PreparedJob.PartIdx];
            if ( Part.PreparedFlag )
            {
               Part.PreparedFlag = false;
               continue;
            }
            RunningTimes = Part.RunningTimes;
            PartId = Part.Id;
            JobStartTime = Part.StartTime;
         }
         else
         {
            if ( Job.PreparedFlag )
            {
               Job.PreparedFlag = false;
               continue;
            }
            RunningTimes = Job.RunningTimes;
            PartId = "";
            JobStartTime = Job.StartTime;
         }
         char JobSeq[18 + 1];
         if ( Job.LoopInterval == 0 )
         {
            HJobSeq::GetJobSeq(JobSeq);
         }
         else
         {
            int MaxCount = Job.LoopCount;
            if ( Job.LoopEndTime > 0 && Job.StartTime > 0 && Job.LoopEndTime > Job.StartTime )
            {
               MaxCount = ( Job.LoopEndTime - Job.LoopStartTime ) / Job.LoopInterval + 1;
               if ( MaxCount > Job.LoopCount )
               {
                  MaxCount = Job.LoopCount;
               }
            }
            HJobSeq::GetJobSeq(JobSeq, MaxCount);
         }
         if ( Job.AutoRerun > 0 )
         {
            if ( RunningTimes > 0 && RunningTimes <= Job.AutoRerun )
            {
               AutoRerun = true;
            }
         }
         if ( Job.AutoRerun > 0 )
         {
            if ( RunningTimes > 0 && RunningTimes <= Job.AutoRerun )
            {
               AutoRerun = true;
            }
         }
         HDispatchInfo Dispatch;
         snprintf(Dispatch.Command, 8192, "export SIMBAT_RUNNING_TIMES=%d;"
            "export SIMBAT_PART='%s';export SIMBAT_PART_TOTAL=%d;export SIMBAT_REGION_ID=%d",
            RunningTimes + 1, ToShellArg(PartId).c_str(), Job.PartVec.size(), PreparedJob.RegionId + 1);
         int CommandLen = strlen(Dispatch.Command);
         if ( Job.RestartFlag )
         {
            snprintf(Dispatch.Command + CommandLen, 8192 - CommandLen, ";export SIMBAT_DATE=%s;export SIMBAT_NEXT_DATE=%s",
               Job.RestartDate.c_str(), Job.RestartNextDate.c_str());
            CommandLen = strlen(Dispatch.Command);
         }
         if ( AutoRerun )
         {
            snprintf(Dispatch.Command + CommandLen, 8192 - CommandLen, ";sleep %d", Job.AutoRerunTime);
            CommandLen = strlen(Dispatch.Command);
         }
         snprintf(Dispatch.Command + CommandLen, 8192 - CommandLen, ";%s", Job.Command.c_str());
         memset(&Dispatch.Input, 0, sizeof(HExecInput));
         BkStrNCpy(Dispatch.Input.BatchTime, LastBatchTime, sizeof(Dispatch.Input.BatchTime) - 1);
         BkStrNCpy(Dispatch.Input.JobSeq, JobSeq, sizeof(Dispatch.Input.JobSeq) - 1);
         Dispatch.Input.Func = efJob;
         BkStrNCpy(Dispatch.Input.FullName, PreparedJob.FullName.c_str(), sizeof(Dispatch.Input.FullName) - 1);
         Dispatch.Input.SrcServerId = MyId;
         Dispatch.Input.DesServerId = PreparedJob.DesServerId;
         Dispatch.Input.JobStartTime = JobStartTime;
         if ( PartFlag )
         {
            part_info &Part = Job.PartVec[PreparedJob.PartIdx];
            Dispatch.Input.RerunFlag = ( Part.RunningTimes > 0 );
            Dispatch.Input.RestartFlag = ( Part.RunningTimes == 0 && Part.RestartFlag );
         }
         else
         {
            Dispatch.Input.RerunFlag = ( Job.RunningTimes > 0 );
            Dispatch.Input.RestartFlag = ( Job.RunningTimes == 0 && Job.RestartFlag );
         }
         if ( PartFlag && PartitionDitherCount > 0 )
         {
            Dispatch.Input.DitherMs = PartitionDitherStep * ( PreparedJob.PartIdx % PartitionDitherCount );
         }
         Dispatch.Input.LoopStartTime = Job.LoopStartTime;
         Dispatch.Input.LoopEndTime = Job.LoopEndTime;
         Dispatch.Input.LoopInterval = Job.LoopInterval;
         Dispatch.Input.LoopCount = Job.LoopCount;
         DispatchQueue.GetByIdx(Header + DispatchIdx) = Dispatch;
         DispatchIdx++;
      }
      SET_ACTION_POINT(RunVec, 0x0020);
      if ( DispatchIdx > 0 )
      {
         DispatchQueue.Add(DispatchIdx);
      }
      if ( i < JobVec.size() )
      {
         BsSleep(50);
      }
      SET_ACTION_POINT(RunVec, 0x0021);
   }

   SET_ACTION_POINT(RunVec, 0x0022);
}

static void Schedule()
{
   SET_ACTION_POINT(Schedule, 0x0001);

   char ErrMsg[2048 + 1];

   if ( HotbackEnableFlag && !HotbackPausedFlag )
   {
      if ( !HotbackObjVec.empty() )
      {
         return;
      }
   }

   if ( ( Batch.Status != bsStarted && Batch.Status != bsStopping ) ||
        ( ClusterFlag && !AnnounceCompleteFlag ) )
   {
      return;
   }

   if ( Batch.Status == bsStopping )
   {
      CheckBatchStop();
      return;
   }

   SET_ACTION_POINT(Schedule, 0x0002);

   time_t ScheduleStartTime = time(NULL);
   bool StatusChangedFlag = true;

   PreparedJobVec.clear();

// 整理待调度作业列表 ScheduleList，删除状态不是等待的作业
// 分区作业调度下标归零
   for ( list<HString>::iterator Iter = ScheduleList.begin();
         Iter != ScheduleList.end(); )
   {
      job_info &Job = Graph[*Iter];
      list<HString>::iterator Next = Iter;
      Next++;
      if ( ( Job.PartFlag && Job.NumOfWaitingPart == 0 ) ||
           ( !Job.PartFlag && Job.Status != jsWaiting ) ||
           Job.BreakpointFlag )
      {
         Job.InScheduleList = false;
         ScheduleList.erase(Iter);
      }
      else
      {
         if ( Job.PartFlag )
         {
            Job.SchedulePartIdx = 0;
         }
      }
      Iter = Next;
   }

   SET_ACTION_POINT(Schedule, 0x0003);

// 计算主机和从机空闲并发数
   int MasterFree = 0;
   vector<int> SlaveFreeVec;

   for ( int i = 0; i < RunningGroupMap.size(); i++ )
   {
      SlaveFreeVec.push_back(0);
   }

   for ( int i = 0; i < ClusterNodeVec.size(); i++ )
   {
      HClusterNodeInfo &Node = ClusterNodeVec[i];
      if ( i == MyId )
      {
         MasterFree += Node.ParaJobNum - ( Node.RunningJobNum - Node.RunningWaitJobNum );
      }
      else
      {
         SlaveFreeVec[Node.GroupIdx] += Node.ParaJobNum - ( Node.RunningJobNum - Node.RunningWaitJobNum );
      }
   }

   SET_ACTION_POINT(Schedule, 0x0004);

   for ( ; ; )
   {
      static vector<HSortJob> SortJobVec;
      SortJobVec.clear();
      int ScheduleListSeq = ScheduleList.size();
      SET_ACTION_POINT(Schedule, 0x0005);
      if ( StatusChangedFlag ) // 需要重新计算待调度作业列表 ScheduleList，可能有新的入度为0的作业产生
      {
         StatusChangedFlag = false;
         // 入度为0的进入SortJobVec
         for ( map<HString, job_info>::iterator Iter = Graph.begin();
              Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( Job.InScheduleList || Job.Type == jtExternJob || Job.Type == jtGroup )
            {
               continue;
            }
            if ( Job.PartFlag && Job.NumOfWaitingPart == 0 )
            {
               continue;
            }
            if ( !Job.PartFlag && ( Job.Status != jsWaiting || Job.PreparedFlag ) )
            {
               continue;
            }
            if ( Job.BreakpointFlag )
            {
               continue;
            }
            int InDegree = Job.InDegree;
            HString Agent = GroupMap[Job.GroupId].Agent;
            for ( ; Agent != ""; Agent = GroupMap[Graph[Agent].GroupId].Agent )
            {
               InDegree += Graph[Agent].InDegree;
            }
            if ( InDegree == 0 )
            {
               HSortJob SortJob;
               SortJob.JobName = Job.Name;
               SortJob.Priority = Job.Priority;
               SortJob.PartitionStartTimeTick = Job.PartitionStartTimeTick;
               SortJob.ScheduleListSeq = ScheduleListSeq;
               SortJob.Random = BkRandom();
               SortJobVec.push_back(SortJob);
               Job.InScheduleList = true;
               if ( Job.PartFlag )
               {
                  Job.SchedulePartIdx = 0;
               }
            }
         }
      }
      SET_ACTION_POINT(Schedule, 0x0006);
      if ( !SortJobVec.empty() || ChangeJobConditionFlag ) // 有新的待调度作业产生
      {
         ChangeJobConditionFlag = false;
// 合并 SortJobVec 到 ScheduleList 并重新排序
         ScheduleListSeq = 0;
         for ( list<HString>::iterator ListIter = ScheduleList.begin();
               ListIter != ScheduleList.end(); ListIter++ )
         {
            job_info &Job = Graph[*ListIter];
            HSortJob SortJob;
            SortJob.JobName = Job.Name;
            SortJob.Priority = Job.Priority;
            SortJob.PartitionStartTimeTick = Job.PartitionStartTimeTick;
            SortJob.ScheduleListSeq = ScheduleListSeq++;
            SortJob.Random = 0;
            SortJobVec.push_back(SortJob);
         }
         sort(SortJobVec.begin(), SortJobVec.end());
         ScheduleList.clear();
         for ( int i = 0; i < SortJobVec.size(); i++ )
         {
            ScheduleList.push_back(SortJobVec[i].JobName);
         }
// 静态条件检查，分区作业第一次执行读入分区文件
         for ( list<HString>::iterator Iter = ScheduleList.begin();
               Iter != ScheduleList.end(); )
         {
            job_info &Job = Graph[*Iter];
            list<HString>::iterator Next = Iter;
            Next++;
            bool AutoSuccessFlag = false;
            char AutoSuccessMsg[500 + 1];
            if ( !AutoSuccessFlag && Job.Command == "" )
            {
               AutoSuccessFlag = true;
               snprintf(AutoSuccessMsg, 500, "Command is empty.");
            }
            if ( !AutoSuccessFlag && Job.SpecialDate != "" && !IsSpecialDate(Job.SpecialDate) )
            {
               AutoSuccessFlag = true;
               snprintf(AutoSuccessMsg, 500, "Special date '%s' is not match.", Job.SpecialDate.c_str());
            }
            if ( !AutoSuccessFlag && Job.ConditionFile != "" )
            {
               if ( Job.PartitionFile == "" ||
                    strstr(Job.ConditionFile.c_str(), "SIMBAT_PART") == NULL )
               {
                  if ( FileExist(EnvEvaluate(Job.ConditionFile)) )
                  {
                     if ( Job.CondNot )
                     {
                        AutoSuccessFlag = true;
                        snprintf(AutoSuccessMsg, 500, "Condition file '%s' is exist.", Job.ConditionFile.c_str());
                     }
                  }
                  else
                  {
                     if ( !Job.CondNot )
                     {
                        AutoSuccessFlag = true;
                        snprintf(AutoSuccessMsg, 500, "Condition file '%s' is not exist.", Job.ConditionFile.c_str());
                     }
                  }
               }
            }
            if ( !AutoSuccessFlag && Job.SpecialDate == "" && Job.ConditionFile == "" )
            {
               bool Condition = true;
               for ( HString JobName = Job.Name; JobName != ""; )
               {
                  if ( !Condition )
                  {
                     break;
                  }
                  group_info &Group = GroupMap[Graph[JobName].GroupId];
                  JobName = Group.Agent;
                  switch ( Group.ConditionDateCache )
                  {
                  case 1 :
                     break;
                  case 2 :
                     if ( Group.SpecialDate == "" )
                     {
                        Group.ConditionDateCache = 3;
                     }
                     else
                     {
                        if ( IsSpecialDate(Group.SpecialDate) )
                        {
                           Group.ConditionDateCache = 3;
                        }
                        else
                        {
                           Group.ConditionDateCache = 4;
                           Condition = false;
                        }
                     }
                     break;
                  case 3 :
                     break;
                  case 4 :
                     Condition = false;
                     break;
                  default :
                     break;
                  }
                  if ( Group.SpecialDate != "" )
                  {
                     break;
                  }
               }
               for ( HString JobName = Job.Name; JobName != ""; )
               {
                  if ( !Condition )
                  {
                     break;
                  }
                  group_info &Group = GroupMap[Graph[JobName].GroupId];
                  JobName = Group.Agent;
                  switch ( Group.ConditionFileCache )
                  {
                  case 1 :
                     break;
                  case 2 :
                     if ( Group.ConditionFile == "" )
                     {
                        Group.ConditionFileCache = 3;
                     }
                     else
                     {
                        if ( FileExist(EnvEvaluate(Group.ConditionFile)) )
                        {
                           if ( Group.CondNot )
                           {
                              Group.ConditionFileCache = 4;
                              Condition = false;
                           }
                           else
                           {
                              Group.ConditionFileCache = 3;
                           }
                        }
                        else
                        {
                           if ( !Group.CondNot )
                           {
                              Group.ConditionFileCache = 4;
                              Condition = false;
                           }
                           else
                           {
                              Group.ConditionFileCache = 3;
                           }
                        }
                     }
                     break;
                  case 3 :
                     break;
                  case 4 :
                     Condition = false;
                     break;
                  default :
                     break;
                  }
                  if ( Group.ConditionFile != "" )
                  {
                     break;
                  }
               }
               if ( !Condition )
               {
                  AutoSuccessFlag = true;
                  snprintf(AutoSuccessMsg, 500, "Special date '%s' or condition file '%s' is not match.",
                     Job.SpecialDate.c_str(), Job.ConditionFile.c_str());
               }
            }
            if ( !AutoSuccessFlag &&
                 Job.LoopEndTime > 0 && Job.LoopEndTime < ScheduleStartTime )
            {
               AutoSuccessFlag = true;
               snprintf(AutoSuccessMsg, 500, "Loop is out of range.");
            }
            if ( AutoSuccessFlag )
            {
               if ( Job.PartFlag )
               {
                  for ( int i = 0; i < Job.PartVec.size(); i++ )
                  {
                     part_info &Part = Job.PartVec[i];
                     if ( Part.Status == jsWaiting )
                     {
                        HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
                        ChangeStatus(FullName, jsRunning, 0, 0, 0);
                        ChangeStatus(FullName, jsSuccess, 0, 0, 0);
                        DoTrigger(FullName, "START" , "SUCCESS");
                     }
                  }
               }
               else
               {
                  ChangeStatus(Job.Name, jsRunning, 0, 0, 0);
                  ChangeStatus(Job.Name, jsSuccess, 0, 0, 0);
                  DoTrigger(Job.Name, "START" , "SUCCESS");
               }
               {
                  char FileName[500 + 1];
                  snprintf(FileName, 500, "%s/log_%s/%s", LogDir.c_str(), LastBatchTime, Job.Name.c_str());
                  long OldSize = BkFileSize(FileName);
                  FILE *f = fopen(FileName, "at");
                  if ( f != NULL )
                  {
                     fprintf(f, "%s\n", AutoSuccessMsg);
                     fclose(f);
                  }
                  snprintf(FileName, 500, "log_%s/%s", LastBatchTime, Job.Name.c_str());
                  PushBackupObj(HBackupObj(boJobLog, FileName, OldSize));
               }
               Job.InScheduleList = false;
               ScheduleList.erase(Iter);
               StatusChangedFlag = true;
               Iter = Next;
               continue;
            }
            if ( Job.LoopStartTime > 0 && Job.LoopStartTime > ScheduleStartTime )
            {
               Job.InScheduleList = false;
               ScheduleList.erase(Iter);
               Iter = Next;
               continue;
            }
            if ( !Job.PartFlag && Job.PartitionFile != "" )
            {
               time_t CurrTime = time(NULL);
               Job.PartitionStartTimeTick = BatchTimeTick++;
               Job.LastStatusChangeTime = CurrTime;
               Job.ServerId = MyId;
               HString PartFileName = EnvEvaluate(Job.PartitionFile);
               FILE *f = fopen(PartFileName.c_str(), "rt");
               if ( f == NULL )
               {
                  char FileName[500 + 1];
                  snprintf(FileName, 500, "%s/log_%s/%s", LogDir.c_str(), LastBatchTime, Job.Name.c_str());
                  long OldSize = BkFileSize(FileName);
                  snprintf(ErrMsg, 2048, "Job %s open %s fail, errno = %d", Job.Name.c_str(), PartFileName.c_str(), errno);
                  Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
                  f = fopen(FileName, "at");
                  if ( f != NULL )
                  {
                     fprintf(f, "%s\n", ErrMsg);
                     fclose(f);
                  }
                  snprintf(FileName, 500, "log_%s/%s", LastBatchTime, Job.Name.c_str());
                  PushBackupObj(HBackupObj(boJobLog, FileName, OldSize));
                  if ( AutoSkip )
                  {
                     snprintf(ErrMsg, 2048, "Auto skip %s", Job.Name.c_str());
                     Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                     ChangeStatus(Job.Name, jsRunning, 0, 0, 0);
                     ChangeStatus(Job.Name, jsFail, 0, 0, 0);
                     ChangeStatus(Job.Name, jsSkipped, 0, 0, 0);
                     DoTrigger(Job.Name, Job.RunningTimes > 1 ? "RERUN" : "START", "FAIL", "SKIP");
                  }
                  else
                  {
                     ChangeStatus(Job.Name, jsRunning, 0, 0, 0);
                     ChangeStatus(Job.Name, jsFail, 0, 0, 0);
                     DoTrigger(Job.Name, Job.RunningTimes > 1 ? "RERUN" : "START" , "FAIL");
                     PushAlarmObj("JOB_FAIL", ErrMsg, Job);
                  }
                  Job.InScheduleList = false;
                  ScheduleList.erase(Iter);
                  StatusChangedFlag = true;
               }
               else
               {
                  set<HString> PartSet;
                  for ( ; ; )
                  {
                     char Line[500 + 1];
                     if ( ReadLine(f, Line, 500) == -1 )
                     {
                        break;
                     }
                     BkRightTrim(Line);
                     if ( Line[0] == '#' || Line[0] == '\0' )
                     {
                        continue;
                     }
                     PartSet.insert(Line);
                  }
                  fclose(f);
                  {
                     char FileName[500 + 1];
                     snprintf(FileName, 500, "%s/part/", ShrDir.c_str());
                     int Len = strlen(FileName);
                     if ( memcmp(PartFileName.c_str(), FileName, Len) == 0 )
                     {
                        snprintf(FileName, 500, "%s/part_bak/%s", ShrDir.c_str(), PartFileName.c_str() + Len);
                        char ErrStr[500 + 1];
                        if ( CopyFile(PartFileName.c_str(), FileName, ErrStr) )
                        {
                           int Size = BkFileSize(FileName);
                           PushBackupObj(HBackupObj(boShr, FileName + ShrDir.length() + 1, Size));
                        }
                        else
                        {
                           snprintf(ErrMsg, 2048, "Copy %s to %s fail, ErrStr = %s", PartFileName.c_str(), FileName, ErrStr);
                           Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
                        }
                     }
                  }
                  {
                     char FileName[500 + 1];
                     snprintf(FileName, 500, "%s/log_%s/%s", LogDir.c_str(), LastBatchTime, Job.Name.c_str());
                     long OldSize = BkFileSize(FileName);
                     f = fopen(FileName, "at");
                     if ( f != NULL )
                     {
                        fprintf(f, "Load partition keys from %s :\n", PartFileName.c_str());
                        for ( set<HString>::iterator Iter = PartSet.begin();
                              Iter != PartSet.end(); Iter++ )
                        {
                           fprintf(f, "%s\n", Iter->c_str());
                        }
                        fclose(f);
                     }
                     snprintf(FileName, 500, "log_%s/%s", LastBatchTime, Job.Name.c_str());
                     PushBackupObj(HBackupObj(boJobLog, FileName, OldSize));
                  }
                  if ( PartSet.empty() )
                  {
                     ChangeStatus(Job.Name, jsRunning, 0, 0, 0);
                     ChangeStatus(Job.Name, jsSuccess, 0, 0, 0);
                     DoTrigger(Job.Name, Job.RunningTimes > 1 ? "RERUN" : "START" , "SUCCESS");
                     Job.InScheduleList = false;
                     ScheduleList.erase(Iter);
                     StatusChangedFlag = true;
                  }
                  else
                  {
#ifdef SIMBAT_ENTERPRISE
                     InitPart_aux(Job, PartSet, CurrTime);
#endif
                  }
               }
            }
            Iter = Next;
         }
         if ( StatusChangedFlag )
         {
            continue;
         }
      }
      SET_ACTION_POINT(Schedule, 0x0007);
      bool Scheduled = false;
      for ( list<HString>::iterator Iter = ScheduleList.begin();
            Iter != ScheduleList.end(); )
      {
         HString FullName;
         job_info &Job = Graph[*Iter];
         list<HString>::iterator Next = Iter;
         Next++;
         if ( BatchRestartFlag && !Job.RestartFlag )
         {
            Iter = Next;
            continue;
         }
         if ( Job.PartFlag )
         {
            if ( Job.ParaJobNum > 0 &&
                 ( Job.NumOfRunningPart + Job.NumOfPreparedPart >= Job.ParaJobNum ) ) // 分区流控
            {
               Iter = Next;
               continue;
            }
            if ( Job.Exclusive &&
               ( Job.NumOfRunningPart > 0 || Job.NumOfPreparedPart > 0 ) ) // 分区互斥
            {
               Iter = Next;
               continue;
            }
            bool Found = false;
            for ( ; Job.SchedulePartIdx < Job.PartVec.size(); )
            {
               part_info &Part = Job.PartVec[Job.SchedulePartIdx];
               if ( Part.Status != jsWaiting || Part.PreparedFlag ||
                    Part.StartTime == ScheduleStartTime ) // 避免作业在同一秒启动两次
               {
                  Job.SchedulePartIdx++;
                  continue;
               }
               if ( Job.ConditionFile != "" )
               {
                  map<HString, HString> EnvMap;
                  EnvMap["SIMBAT_PART"] = Part.Id;
                  bool ExistFlag = FileExist(EnvEvaluate(Job.ConditionFile, &EnvMap));
                  if ( ( ExistFlag && Job.CondNot ) ||
                       ( !ExistFlag && !Job.CondNot ) )
                  {
                     HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
                     ChangeStatus(FullName, jsRunning, 0, 0, 0);
                     ChangeStatus(FullName, jsSuccess, 0, 0, 0);
                     DoTrigger(FullName, "START" , "SUCCESS");
                     Scheduled = true;
                     Job.SchedulePartIdx++;
                     continue;
                  }
               }
               FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
               Job.SchedulePartIdx++;
               Found = true;
               break;
            }
            if ( !Found )
            {
               Job.InScheduleList = false;
               ScheduleList.erase(Iter);
               StatusChangedFlag = true;
               Iter = Next;
               continue;
            }
         }
         else
         {
            if ( Job.PreparedFlag ||
                 Job.StartTime == ScheduleStartTime ) // 避免作业在同一秒启动两次
            {
               Iter = Next;
               continue;
            }
            FullName = Job.Name;
         }
         if ( Job.BusinessType != "" && BusinessTrafficControlMap.find(Job.BusinessType) != BusinessTrafficControlMap.end() ) // 业务流控
         {
            HBusinessTrafficControl &Control = BusinessTrafficControlMap[Job.BusinessType];
            if ( Control.RunningJobNum + Control.PreparedJobNum >= Control.ParaJobNum )
            {
               Iter = Next;
               continue;
            }
         }
         if ( Job.GroupTrafficControlFlag && Job.Type != jtEventWait && Job.Type != jtTime ) // 组流控
         {
            bool ControlFlag = false;
            for ( int i = 0; i < Job.UpperGroupVec.size(); i++ )
            {
               group_info &Group = GroupMap[Job.UpperGroupVec[i]];
               if ( Group.ParaJobNum == 0 )
               {
                  continue;
               }
               if ( Group.RunningJobNum + Group.PreparedJobNum -
                    Group.RunningWaitJobNum >= Group.ParaJobNum )
               {
                  ControlFlag = true;
                  break;
               }
            }
            if ( ControlFlag )
            {
               Iter = Next;
               continue;
            }
         }
         if ( Job.Anchor ) // 总流控
         {
            if ( MasterFree <= 0 )
            {
               Iter = Next;
               continue;
            }
            MasterFree--;
         }
         else // 考虑多运行组
         {
            if ( SlaveFreeVec[Job.RunningGroupIdx] <= 0 &&
                 ( Job.RunningGroupIdx > 0 || MasterFree <= 0 || DedicatedFlag ) )
            {
               Iter = Next;
               continue;
            }
            if ( SlaveFreeVec[Job.RunningGroupIdx] <= 0 )
            {
               MasterFree--;
            }
            else
            {
               SlaveFreeVec[Job.RunningGroupIdx]--;
            }
         }
// 放入待运行数组
         HPreparedJob PreparedJob;
         PreparedJob.FullName = FullName;
         PreparedJob.PartFlag = Job.PartFlag;
         if ( Job.PartFlag )
         {
            PreparedJob.PartIdx = Job.SchedulePartIdx - 1;
         }
         PreparedJob.JobPtr = &Job;
         PreparedJobVec.push_back(PreparedJob);
         if ( Job.PartFlag )
         {
            part_info &Part = Job.PartVec[PreparedJob.PartIdx];
            Part.PreparedFlag = true;
            Job.NumOfPreparedPart++;
         }
         else
         {
            Job.PreparedFlag = true;
         }
         if ( Job.BusinessType != "" && BusinessTrafficControlMap.find(Job.BusinessType) != BusinessTrafficControlMap.end() ) // 业务流控
         {
            HBusinessTrafficControl &Control = BusinessTrafficControlMap[Job.BusinessType];
            Control.PreparedJobNum++;
         }
         if ( Job.Type != jtEventWait && Job.Type != jtTime )
         {
            for ( int i = 0; i < Job.UpperGroupVec.size(); i++ )
            {
               group_info &Group = GroupMap[Job.UpperGroupVec[i]];
               Group.PreparedJobNum++;
            }
         }
         Scheduled = true;
         Iter = Next;
      }
      SET_ACTION_POINT(Schedule, 0x0008);
      if ( !Scheduled )
      {
         break;
      }
   }

   SET_ACTION_POINT(Schedule, 0x0009);
   RunVec(PreparedJobVec);
   SET_ACTION_POINT(Schedule, 0x000a);
   CheckBatchStop();
   SET_ACTION_POINT(Schedule, 0x000b);
   gettimeofday(&LastScheduleTime, NULL);
   int DeltaTime = LastScheduleTime.tv_sec - ScheduleStartTime;

   if ( DeltaTime >= 3 )
   {
      snprintf(ErrMsg, 2048, "Schedule end, count = %d, elapsed = %d", PreparedJobVec.size(), DeltaTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
   }

   SET_ACTION_POINT(Schedule, 0x000c);
}

static void FlushInput()
{
   char ErrMsg[2048 + 1];
   int Header = 0;
   int Qty = 0;
   time_t FlushInputStartTime = time(NULL);

   InputQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty && !ExitFlag; i++ )
   {
      const HInputInfo &Input = InputQueue.GetByIdx(Header + i);
      GraphMutex.Lock();
      FuncArray[Input.Func](Input);
      GraphMutex.Unlock();
      HLogLevel LogLevel = Input.Func == fcSlaveSignon || Input.Func == fcInquiry ||
         Input.Func == fcJobSuccess || Input.Func == fcJobRunning ||
         Input.Func == fcExecuteCommand || Input.Func == fcLogin ||
         Input.Func == fcOutline ||
         Input.Func == fcBackupFile || Input.Func == fcTouchGateway ||
         Input.Func == fcDatabase || Input.Func == fcPushOutline ? llDebug : llInfo;
      if ( Syslog.CheckLevel(LogLevel) )
      {
         snprintf(ErrMsg, 2048, "Process completed, Func = %s, Object = %s",
            GetFuncCodeString(Input.Func), Input.Func == fcLogin ? "*" : Input.ObjName.c_str());
         Syslog.Log(__FILE__, __LINE__, LogLevel, "EXIT", ErrMsg);
      }
   }

   InputQueue.Remove(Qty);

   int DeltaTime = time(NULL) - FlushInputStartTime;

   if ( DeltaTime >= 3 )
   {
      snprintf(ErrMsg, 2048, "FlushInput end, count = %d, elapsed = %d", Qty, DeltaTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
   }
}

static void FlushStatus()
{
   char ErrMsg[2048 + 1];
   time_t FlushStatusStartTime = time(NULL);
   int Header = 0;
   int Qty = 0;

   StatusInputQueue.GetCurrForRead(Header, Qty);

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused &&
        Batch.Status != bsStopping )
   {
      StatusInputQueue.Remove(Qty);
      return;
   }

   for ( int i = 0; i < Qty; i++ )
   {
      const HStatusInputInfo &Input = StatusInputQueue.GetByIdx(Header + i);
      const HString FullName = Input.FullName;
      GraphMutex.Lock();
      if ( Syslog.CheckLevel(llDebug) )
      {
         snprintf(ErrMsg, 2048, "JobStatus(%s, %s, %s, %d, %s)",
            GetFuncCodeString(Input.Func), FullName.c_str(),
            TimeToString(Input.JobStartTime).c_str(),
            Input.Pid, Input.IpAddr);
         Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      }
      int PartIdx = Input.PartIdx;
      bool PartFlag = Input.PartFlag;
      job_info &Job = *Input.JobPtr;
      int ServerId = 0;
      if ( ( PartFlag && !Job.PartFlag ) || ( !PartFlag && Job.PartFlag ) )
      {
         snprintf(ErrMsg, 2048, "Job %s PartFlag(%d, %d) not match, Func = %s", 
            FullName.c_str(), PartFlag, Job.PartFlag, GetFuncCodeString(Input.Func));
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         GraphMutex.Unlock();
         continue;
      }
      if ( PartFlag )
      {
         part_info &Part = Job.PartVec[PartIdx];
         ServerId = Part.ServerId;
         if ( Part.StartTime == Input.JobStartTime )
         {
            if ( Input.Func == fcJobUnknown )
            {
               Part.SlaveNotSignon = false;
            }
         }
         else
         {
            snprintf(ErrMsg, 2048, "StartTime not match, Func = %s, Job = %s, JobStartTime = %s, %s, IpAddr = %s",
               GetFuncCodeString(Input.Func), FullName.c_str(),
               TimeToString(Part.StartTime).c_str(),
               TimeToString(Input.JobStartTime).c_str(), Input.IpAddr);
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            GraphMutex.Unlock();
            continue;
         }
      }
      else
      {
         ServerId = Job.ServerId;
         if ( Job.StartTime == Input.JobStartTime )
         {
            if ( Input.Func == fcJobUnknown )
            {
               Job.SlaveNotSignon = false;
            }
         }
         else
         {
            snprintf(ErrMsg, 2048, "StartTime not match, Func = %s, Job = %s, JobStartTime = %s, %s, IpAddr = %s",
               GetFuncCodeString(Input.Func), FullName.c_str(),
               TimeToString(Job.StartTime).c_str(),
               TimeToString(Input.JobStartTime).c_str(), Input.IpAddr);
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            GraphMutex.Unlock();
            continue;
         }
      }
      if ( Input.Func == fcJobSuccess )
      {
         time_t CurrTime = time(NULL);
         if ( PartFlag )
         {
            part_info &Part = Job.PartVec[PartIdx];
            if ( CurrTime - Part.LastUserStopTime <= 2 )
            {
               Input.Func == fcJobFail;
            }
            Part.LastUserStopTime = 0;
         }
         else
         {
            if ( CurrTime - Job.LastUserStopTime <= 2 )
            {
               Input.Func == fcJobFail;
            }
            Job.LastUserStopTime = 0;
         }
         if ( Job.LoopConditionVariable != "" )
         {
            HString VarName = EnvEvaluate(Job.LoopConditionVariable);
            if ( Input.SetConditionFlag )
            {
               SetEnv(VarName.c_str(), "Y");
            }
            else
            {
               SetEnv(VarName.c_str(), "N");
            }
         }
      }
      switch ( Input.Func )
      {
      case fcJobRunning :
         ChangeStatus(FullName, jsRunning, 0, 0, Input.Pid);
         break;
      case fcJobCancel :
         ChangeStatus(FullName, jsWaiting, 0, 0, 0);
         break;
      case fcJobSuccess :
         ChangeStatus(FullName, jsSuccess, 0, 0, Input.Pid);
         break;
      case fcJobFail :
         {
            snprintf(ErrMsg, 2048, "Job %s fail", FullName.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            ChangeStatus(FullName, jsFail, 0, 0, Input.Pid);
            if ( Job.StopAndSkipFlag )
            {
               ChangeStatus(FullName, jsSkipped, 0, 0, 0);
            }
            else
            {
               bool RerunFlag = false;
               if ( PartFlag )
               {
                  part_info &Part = Job.PartVec[PartIdx];
                  if ( Job.AutoRerun > 0 && Part.LastUserStopTime == 0 && Part.RunningTimes <= Job.AutoRerun )
                  {
                     RerunJob(Job, PartIdx);
                     RerunFlag = true;
                  }
                  Part.LastUserStopTime = 0;
               }
               else
               {
                  if ( Job.AutoRerun > 0 && Job.LastUserStopTime == 0 && Job.RunningTimes <= Job.AutoRerun )
                  {
                     RerunJob(Job, -1);
                     RerunFlag = true;
                  }
                  Job.LastUserStopTime = 0;
               }
               if ( !RerunFlag && AutoSkip )
               {
                  snprintf(ErrMsg, 2048, "Auto skip %s", FullName.c_str());
                  Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                  ChangeStatus(FullName, jsSkipped, 0, 0, 0);
               }
            }
         }
         break;
      case fcJobUnknown :
         ChangeStatus(FullName, jsUnknown, 0, 0, 0);
         break;
      }
      GraphMutex.Unlock();
   }

   StatusInputQueue.Remove(Qty);

   int DeltaTime = time(NULL) - FlushStatusStartTime;

   if ( DeltaTime >= 3 )
   {
      snprintf(ErrMsg, 2048, "FlushStatus end, count = %d, elapsed = %d", Qty, DeltaTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
   }
}

static void SlaveFlushStatus()
{
   char ErrMsg[2048 + 1];
   time_t FlushStatusStartTime = time(NULL);
   int Header = 0;
   int Qty = 0;

   StatusInputQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty; i++ )
   {
      const HStatusInputInfo &Input = StatusInputQueue.GetByIdx(Header + i);
      GraphMutex.Lock();
      map<HString, HRunningJobInfo>::iterator Iter = RunningJobMap.find(Input.FullName);
      if ( Iter == RunningJobMap.end() )
      {
         GraphMutex.Unlock();
         continue;
      }
      HRunningJobInfo &Job = Iter->second;
      if ( Job.JobStartTime != Input.JobStartTime )
      {
         GraphMutex.Unlock();
         continue;
      }
      if ( Input.Func == fcJobSuccess )
      {
         Job.Status = jsSuccess;
      }
      if ( Input.Func == fcJobFail )
      {
         Job.Status = jsFail;
      }
      GraphMutex.Unlock();
   }

   StatusInputQueue.Remove(Qty);

   int DeltaTime = time(NULL) - FlushStatusStartTime;

   if ( DeltaTime >= 3 )
   {
      snprintf(ErrMsg, 2048, "SlaveFlushStatus end, count = %d, elapsed = %d", Qty, DeltaTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
   }
}

/*
REQUEST FROM MONITOR
   Func   : start_batch
   Input  : '1' + { code_set, id, batch_name, batch_date }
   Output : '0'(success) |  '1'(fail)

   Func   : pause_batch
   Input  : '2' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : stop_batch
   Input  : '3' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : inquiry
   Input  : '4' + { code_set, id, compress_flag, last_refresh_tick }
   Output : 8 byte length(hex)  + xml

   Func   : skip_job
   Input  : '5' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : rerun_job
   Input  : '6' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : stop_job
   Input  : '7' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : view_log
   Input  : '8' + { code_set, id, job_name }
   Output : 8 byte length(hex)  + log

   Func   : view_summary
   Input  : '9' + { code_set, id }
   Output : 8 byte length(hex)  + log

   Func   : login
   Input  : 'A' + { code_set, id, password, action }
   Output : '0'(success) |  '1'(fail)

   Func   : continue_batch
   Input  : 'B' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : fire_event
   Input  : 'C' + { code_set, id, event_name }
   Output : '0'(success) |  '1'(fail)

   Func   : restore_job
   Input  : 'D' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : view_additional_log
   Input  : 'E' + { code_set, id, job_name }
   Output : 8 byte length(hex)  + log

   Func   : calc_critical_path
   Input  : 'F' + { code_set, id }
   Output : 8 byte length(hex)  + critical_path

   Func   : upload sbt
   Input  : 'G' + { code_set, id, filename, password }
                + { file }

   Func   : outline
   Input  : 'H' + { code_set, id, compress_flag }
   Output : 8 byte length(hex)  + xml

   Func   : next_batch
   Input  : 'I' + { code_set, id, batch_name, batch_date }
   Output : '0'(success) |  '1'(fail)

   Func   : set_para_job_num
   Input  : 'J' + { code_set, id, para_job_num }
   Output : '0'(success) |  '1'(fail)

   Func   : restart_job
   Input  : 'K' + { code_set, id, job_name, batch_date }
   Output : '0'(success) |  '1'(fail)

   Func   : dump
   Input  : 'L' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : configure
   Input  : 'M' + { code_set, id, append_log_to_summary, para_job_num, server_code_set, daily_batch, batch_alarm, dedicated }
   Output : '0'(success) |  '1'(fail)

   Func   : reload_config
   Input  : 'N' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : change_time_diff
   Input  : 'O' + { code_set, id, time_diff }
   Output : '0'(success) |  '1'(fail)

   Func   : master_ascend
   Input  : 'P' + { code_set, id }
   Output : '0'(success) |  '1'(fail)

   Func   : slave_signon
   Input  : 'Q' + { code_set, id, node_id, var_pool_version, start_time }
   Output : '0'(success) + var_pool_version + curr_time + config_version + last_batch_time |  '1'(fail)

   Func   : touch_gateway
   Input  : 'R' + { code_set, id, system }
   Output : '0'(success) + config_version |  '1'(fail)

   Func   : execute_command
   Input  : 'S' + { code_set, id, job_name, src_server_id, des_server_id, job_start_time, comm_start_time,
                    rerun_flag, restart_flag, loop_start_time, loop_end_time, loop_interval, command }
   Output : '0'(success) |  '1'(fail)

   Func   : kill_process
   Input  : 'T' + { code_set, id, job_name, job_start_time, src_server_id }
   Output : '0'(success) |  '1'(fail)

   Func   : download
   Input  : 'U' + { code_set, id, filename, compress_flag }
   Output : 8 byte length(hex)  + xml

   Func   : inquiry_command
   Input  : 'V' + { code_set, id, job_name, job_start_time, src_server_id }
   Output : '0'(success) |  '1'(fail)

   Func   : announce_complete
   Input  : 'W' + { code_set, id, success_flag }
   Output : '0'(success) |  '1'(fail)

   Func   : database
   Input  : 'X' + { code_set, id, sub_func, system, ... }
   Output : '0'(success) |  '1'(fail)


   Func   : job_running
   Input  : 'a' + { code_set, id, job_name, job_start_time, pid }
   Output : '0'(success) |  '1'(fail)

   Func   : job_cancel
   Input  : 'b' + { code_set, id, job_name, job_start_time }
   Output : '0'(success) |  '1'(fail)

   Func   : job_success
   Input  : 'c' + { code_set, id, job_name, job_start_time, pid, set_condition_flag }
   Output : '0'(success) |  '1'(fail)

   Func   : job_fail
   Input  : 'd' + { code_set, id, job_name, job_start_time, pid }
   Output : '0'(success) |  '1'(fail)

   Func   : restore_job_success
   Input  : 'e' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : restore_job_fail
   Input  : 'f' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

   Func   : set_var
   Input  : 'g' + { code_set, id, var_name, value }
   Output : '0'(success) |  '1'(fail)

   Func   : job_unknown
   Input  : 'h' + { code_set, id, job_name, job_start_time }
   Output : '0'(success) |  '1'(fail)

   Func   : change_job_condition
   Input  : 'j' + { code_set, id, job_name, cond_not, condition_file, special_date }
   Output : '0'(success) |  '1'(fail)

   Func   : backup_init
   Input  : 'l' + { code_set, id, last_batch_time }
   Output : '0'(success) + job_event_qty |  '1'(fail)

   Func   : backup_file
   Input  : 'm' + { code_set, id, obj_type, obj_name, offset, len, file }
   Output : '0'(success) |  '1'(fail)

   Func   : change_date
   Input  : 'n' + { code_set, id, batch_date, next_batch_date }
   Output : '0'(success) |  '1'(fail)

   Func   : get_seq
   Input  : 'o' + { code_set, id }
   Output : '0'(success) + job_seq |  '1'(fail)

   Func   : push_alarm
   Input  : 'p' + { code_set, id, system, time, seq, date, type, msg }
   Output : '0'(success) |  '1'(fail)

   Func   : push_outline
   Input  : 'q' + { code_set, id, system, ... }
   Output : '0'(success) |  '1'(fail)

   Func   : breakpoint
   Input  : 'r' + { code_set, id, job_name, set/unset }
   Output : '0'(success) |  '1'(fail)

   Func   : get_var
   Input  : 's' + { code_set, id, var_name }
   Output : '0'(success) + var_value |  '1'(fail)

   Func   : step
   Input  : 't' + { code_set, id, job_name }
   Output : '0'(success) |  '1'(fail)

*/

static int GetInput(HInputInfo &Input)
{
   int r;
   int Len;
   char IpAddr[200 + 1];
   char ErrMsg[2048 + 1];
   char TmpStr[8192 + 1];

   Input = HInputInfo();
   Input.Fd = BsAcceptConnection(SockSch, IpAddr, 500);

   if ( Input.Fd == BS_ETIMEOUT )
   {
      return -3;
   }

   if ( Input.Fd < 0 )
   {
      int Errno = errno;
      snprintf(ErrMsg, 2048, "BsAcceptConnection fail, r = %d, errno = %d", Input.Fd, Errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      if ( Errno == ECONNABORTED || Errno == ECONNRESET )
      {
         return 0;
      }
      return -1;
   }

   Input.IpAddr = IpAddr;

   r = ReadFromSocketHSL(Input.Fd, TmpStr, 8192, COMM_TIME_OUT);

   if ( r < 0 )
   {
      int Errno = errno;
      snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, IP = %s, r = %d, errno = %d", IpAddr, r, Errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Input.Fd);
      if ( Errno == ECONNABORTED || Errno == ECONNRESET )
      {
         return 0;
      }
      return -1;
   }

   TmpStr[r] = '\0';

   if ( r == 0 )
   {
      return 0;
   }

   if ( memcmp(TmpStr, "SIMJIE0104", 10) != 0 )
   {
      snprintf(ErrMsg, 2048, "Protocal version '%s' not match, IP = %s", TmpStr, IpAddr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Input.Fd);
      return 0;
   }

   switch( TmpStr[10] )
   {
   case '1' :
      Input.Func = fcStartBatch;
      break;
   case '2' :
      Input.Func = fcPauseBatch;
      break;
   case '3' :
      Input.Func = fcStopBatch;
      break;
   case '4' :
      Input.Func = fcInquiry;
      break;
   case '5' :
      Input.Func = fcSkipJob;
      break;
   case '6' :
      Input.Func = fcRerunJob;
      break;
   case '7' :
      Input.Func = fcStopJob;
      break;
   case '8' :
      Input.Func = fcViewLog;
      break;
   case '9' :
      Input.Func = fcViewSummary;
      break;
   case 'A' :
      Input.Func = fcLogin;
      break;
   case 'B' :
      Input.Func = fcResumeBatch;
      break;
   case 'C' :
      Input.Func = fcFireEvent;
      break;
   case 'D' :
      Input.Func = fcRestoreJob;
      break;
   case 'E' :
      Input.Func = fcViewAdditionalLog;
      break;
   case 'F' :
      Input.Func = fcCalcCriticalPath;
      break;
   case 'G' :
      Input.Func = fcUploadSbt;
      break;
   case 'H' :
      Input.Func = fcOutline;
      break;
   case 'I' :
      Input.Func = fcNextBatch;
      break;
   case 'J' :
      Input.Func = fcSetParaJobNum;
      break;
   case 'K' :
      Input.Func = fcRestartJob;
      break;
   case 'L' :
      Input.Func = fcDump;
      break;
   case 'M' :
      Input.Func = fcConfigure;
      break;
   case 'N' :
      Input.Func = fcReloadConfig;
      break;
   case 'P' :
      Input.Func = fcMasterAscend;
      break;
   case 'Q' :
      Input.Func = fcSlaveSignon;
      break;
   case 'R' :
      Input.Func = fcTouchGateway;
      break;
   case 'S' :
      Input.Func = fcExecuteCommand;
      break;
   case 'T' :
      Input.Func = fcKillProcess;
      break;
   case 'U' :
      Input.Func = fcDownload;
      break;
   case 'V' :
      Input.Func = fcInquiryCommand;
      break;
   case 'W' :
      Input.Func = fcAnnounceComplete;
      break;
   case 'X' :
      Input.Func = fcDatabase;
      break;
   case 'a' :
      Input.Func = fcJobRunning;
      break;
   case 'b' :
      Input.Func = fcJobCancel;
      break;
   case 'c' :
      Input.Func = fcJobSuccess;
      break;
   case 'd' :
      Input.Func = fcJobFail;
      break;
   case 'e' :
      Input.Func = fcRestoreJobSuccess;
      break;
   case 'f' :
      Input.Func = fcRestoreJobFail;
      break;
   case 'g' :
      Input.Func = fcSetVar;
      break;
   case 'h' :
      Input.Func = fcJobUnknown;
      break;
   case 'j' :
      Input.Func = fcChangeJobCondition;
      break;
   case 'k' :
      Input.Func = fcStopAndSkipJob;
      break;
   case 'l' :
      Input.Func = fcBackupInit;
      break;
   case 'm' :
      Input.Func = fcBackupFile;
      break;
   case 'n' :
      Input.Func = fcChangeDate;
      break;
   case 'o' :
      Input.Func = fcGetSeq;
      break;
   case 'p' :
      Input.Func = fcPushAlarm;
      break;
   case 'q' :
      Input.Func = fcPushOutline;
      break;
   case 'r' :
      Input.Func = fcBreakpoint;
      break;
   case 's' :
      Input.Func = fcGetVar;
      break;
   case 't' :
      Input.Func = fcStep;
      break;
   default :
      snprintf(ErrMsg, 2048, "Invalid tcp func(%02x, %c), IP = %s", TmpStr[10], TmpStr[10], IpAddr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Input.Fd);
      return -1;
   }

   SplitString(TmpStr, '|', Input.ArgVec, true);

   if ( Input.ArgVec.size() > 1 )
   {
      Input.CodeSet = Input.ArgVec[1];
   }

   if ( Input.ArgVec.size() > 2 )
   {
      Input.Id = Input.ArgVec[2];
   }

   if ( Input.ArgVec.size() > 3 )
   {
      BkStrNCpy(TmpStr, Input.ArgVec[3].c_str(), sizeof(TmpStr) - 1);
      ConvString(StrToCodeSet(Input.CodeSet), CodeSet, TmpStr);
      Input.ObjName = TmpStr;
   }

   return 0;
}

static void ReloadStatus(job_info &Job, int PartIdx, HJobStatus Status, int ServerId, int RegionId, pid_t Pid, time_t StatusChangeTime)
{
   part_info *Part = &Job.PartVec[PartIdx];
   HJobStatus OldStatus = Part->Status;

   Job.LastStatusChangeTime = StatusChangeTime;

   if ( Status == jsWaiting && ServerId == 88888 ) // Restart
   {
      if ( Batch.Status == bsStopped )
      {
         BatchRestartFlag = true;
         ChangeBatchStatus(bsStarted);
      }
      Part->RunningTimes = 0;
      Part->RestartFlag = true;
      Job.RestartFlag = true;
      Job.Step = 0;
   }

   if ( Status == jsRunning && ServerId == 88888 ) // Step
   {
      Job.Step++;
   }

   if ( Part->Pid == 0 && Pid != 0 )
   {
      Part->RunningTimes++;
      Part->Pid = Pid;
   }

   if ( Status == jsRunning && Pid == 0 )
   {
      Part->ServerId = ServerId;
      Part->Pid = 0;
   }

   Part->LastStatusChangeTime = StatusChangeTime;
   Part->SlaveNotSignon = false;

   if ( Status == OldStatus )
   {
      return;
   }

   if ( OldStatus == jsWaiting )
   {
      Job.NumOfWaitingPart--;
   }

   HClusterNodeInfo &Node = ClusterNodeVec[Part->ServerId];

   if ( OldStatus == jsRunning || OldStatus == jsUnknown )
   {
      Node.RunningJobNum--;
      if ( OldStatus == jsRunning )
      {
         Node.RegionJobNumVec[Part->RegionId]--;
      }
      if ( Job.Type == jtEventWait || Job.Type == jtTime )
      {
         Node.RunningWaitJobNum--;
      }
   }

   if ( OldStatus == jsRunning )
   {
      if ( Job.NumOfRunningPart == 1 )
      {
         Job.IdleTimeBegin = StatusChangeTime;
      }
      Job.NumOfRunningPart--;
      RunningJobNum--;
   }

   if ( OldStatus == jsSuccess || OldStatus == jsSkipped )
   {
      Job.NumOfCompletedPart--;
   }

   if ( OldStatus == jsFail || OldStatus == jsUnknown )
   {
      Job.NumOfFailedPart--;
      if ( OldStatus == jsUnknown )
      {
         Job.NumOfUnknownPart--;
      }
   }

   if ( Status == jsRunning && Pid == 0 )
   {
      Part->ServerId = ServerId;
      Part->RegionId = RegionId;
      for ( int i = Node.RegionJobNumVec.size(); i <= RegionId; i++ )
      {
         Node.RegionJobNumVec.push_back(0);
      }
   }

   if ( Status == jsRunning || Status == jsUnknown )
   {
      Node.RunningJobNum++;
      if ( Status == jsRunning )
      {
         Node.RegionJobNumVec[Part->RegionId]++;
      }
      if ( Job.Type == jtEventWait || Job.Type == jtTime )
      {
         Node.RunningWaitJobNum++;
      }
   }

   if ( OldStatus == jsWaiting && Status == jsRunning )
   {
      Part->StartTime = StatusChangeTime;
      if ( Part->BeginningTime == 0 )
      {
         Part->BeginningTime = Part->StartTime;
      }
      Part->EndTime = 0;
   }

   if ( Status == jsRunning )
   {
      Part->LastUserStopTime = 0;
      RunningJobNum++;
   }

   if ( Status == jsSuccess || Status == jsFail ||
        Status == jsUnknown || Status == jsSkipped )
   {
      Part->EndTime = StatusChangeTime;
   }

   if ( Status == jsWaiting )
   {
      Job.NumOfWaitingPart++;
   }

   if ( Status == jsRunning )
   {
      Job.NumOfRunningPart++;
      if ( Job.IdleTimeBegin > 0 )
      {
         Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
         Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
         Job.IdleTimeBegin = 0;
      }
   }

   if ( Status == jsSuccess || Status == jsSkipped )
   {
      Job.NumOfCompletedPart++;
   }

   if ( Status == jsFail || Status == jsUnknown )
   {
      Job.NumOfFailedPart++;
      if ( Status == jsUnknown )
      {
         Job.NumOfUnknownPart++;
      }
   }

   Part->Status = Status;

   if ( Job.Type == jtJob &&
        ( Status == jsFail || Status == jsUnknown ) &&
        Job.RestoreCommand != "" )
   {
      Part->RestoreStatus = rsNeedRestore;
   }

   if ( RebuildSummary &&
        ( Status == jsSuccess || Status == jsSkipped ||
          Status == jsFail || Status == jsUnknown ) )
   {
      WriteSummary(Job, Status, PartIdx);
   }
}

static void ChangeStatus(const HString &FullName, HJobStatus Status, int ServerId, int RegionId,
   pid_t Pid, const char *CurrTime)
{
   SET_ACTION_POINT(ChangeStatus, 0x0001);

   char ErrMsg[2048 + 1];

   if ( Syslog.CheckLevel(llDebug) )
   {
      snprintf(ErrMsg, 2048, "ChangeStatus(%s, %s, %d, %d, %d, %s)",
         FullName.c_str(), JobStatusToString(Status), ServerId, RegionId, Pid,
         CurrTime == NULL ? "NULL" : CurrTime);
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
   }

   char TmpStr[500 + 1];
   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(FullName, JobName, PartIdx);

   char Time[80 + 1];

   SET_ACTION_POINT(ChangeStatus, 0x0002);
   job_info &Job = Graph[JobName];
   part_info *Part = NULL;
   HXmlTree *PartXml = NULL;
   HJobStatus OldStatus;
   HJobStatus JobOldStatus;
   HJobStatus PartOldStatus;
   time_t StatusChangeTime = 0;

   if ( CurrTime == NULL )
   {
      BkCurrTime(Time);
      StatusChangeTime = time(NULL);
   }
   else
   {
      BkStrNCpy(Time, CurrTime, sizeof(Time) - 1);
      StatusChangeTime = StringToTime(Time);
   }

   SET_ACTION_POINT(ChangeStatus, 0x0003);

   if ( PartFlag && !Job.PartFlag )
   {
      snprintf(ErrMsg, 2048, "Job '%s' is not partitional, fullname = %s, status = %s",
         JobName.c_str(), FullName.c_str(), JobStatusToString(Status));
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   bool PartJobFlag = Job.PartFlag && !PartFlag;

   if ( PartFlag )
   {
      Part = &Job.PartVec[PartIdx];
      PartXml = Part->Xml;
   }

   JobOldStatus = Job.Status;

   if ( PartFlag )
   {
      PartOldStatus = Part->Status;
      OldStatus = PartOldStatus;
   }
   else
   {
      OldStatus = JobOldStatus;
   }

   SET_ACTION_POINT(ChangeStatus, 0x0004);

   if ( Status == jsWaiting && ServerId == 88888 ) // Restart
   {
      if ( Batch.Status == bsStopped )
      {
         BatchRestartFlag = true;
         ChangeBatchStatus(bsStarted);
      }
      Job.RestartFlag = true;
      if ( PartFlag )
      {
         Part->RunningTimes = 0;
         Part->RestartFlag = true;
      }
      else
      {
         Job.RunningTimes = 0;
      }
      Job.Step = 0;
   }

   if ( Status == jsRunning && ServerId == 88888 ) // Step
   {
      Job.Step++;
   }

   SET_ACTION_POINT(ChangeStatus, 0x0005);

   bool RestartFlag;

   if ( PartFlag )
   {
      RestartFlag = Part->RestartFlag;
   }
   else
   {
      RestartFlag = Job.RestartFlag;
   }

   if ( Status == OldStatus && ServerId != 88888 )
   {
      if ( Status != jsRunning )
      {
         snprintf(ErrMsg, 2048, "Job '%s', status = %s, old status = %s, ignore it",
            FullName.c_str(), JobStatusToString(Status), JobStatusToString(OldStatus));
         Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
         return;
      }
      if ( PartFlag )
      {
         if ( Part->Pid != 0 )
         {
            snprintf(ErrMsg, 2048, "Job '%s', status = %s, old status = %s, ignore it",
               FullName.c_str(), JobStatusToString(Status), JobStatusToString(OldStatus));
            Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
            return;
         }
      }
      else
      {
         if ( Job.Pid != 0 )
         {
            snprintf(ErrMsg, 2048, "Job '%s', status = %s, old status = %s, ignore it",
               FullName.c_str(), JobStatusToString(Status), JobStatusToString(OldStatus));
            Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
            return;
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0006);

   if ( Status == jsRunning && ServerId != 88888 )
   {
      if ( ( Pid != 0 && OldStatus == jsWaiting ) ||  // jsFail 后发先至，且AutoRerun，丢弃jsRunning
           ( OldStatus != jsWaiting && OldStatus != jsRunning &&
             OldStatus != jsUnknown && Job.Type != jtGroup && !PartJobFlag ) )
      {
         snprintf(ErrMsg, 2048, "Job '%s', status = %s, old status = %s, ignore it",
            FullName.c_str(), JobStatusToString(Status), JobStatusToString(OldStatus));
         Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
         return;
      }
   }

   if ( Status == jsUnknown && OldStatus != jsRunning )
   {
      snprintf(ErrMsg, 2048, "Job '%s', status = %s, old status = %s, ignore it",
         FullName.c_str(), JobStatusToString(Status), JobStatusToString(OldStatus));
      Syslog.Log(__FILE__, __LINE__, llDebug, "OTHER", ErrMsg);
      return;
   }

   SET_ACTION_POINT(ChangeStatus, 0x0007);

   bool TriggerFlag = ( ( JobTrigger != "" || Job.JobTrigger != "" ) && CurrTime == NULL );

   if ( Pid != 0 )
   {
      bool TriggerStartFlag = false;
      bool RerunFlag = false;
      if ( PartFlag )
      {
         if ( Part->Pid == 0 )
         {
            Part->RunningTimes++;
            Part->Pid = Pid;
            PartXml->SetProperty("PID", Pid);
            TriggerStartFlag = true;
         }
         RerunFlag = ( Part->RunningTimes > 1 );
      }
      else
      {
         if ( Job.Pid == 0 )
         {
            Job.RunningTimes++;
            Job.Pid = Pid;
            Job.Xml->SetProperty("PID", Pid);
            TriggerStartFlag = true;
         }
         RerunFlag = ( Job.RunningTimes > 1 );
      }
      if ( TriggerFlag )
      {
         switch ( Status )
         {
         case jsRunning :
            DoTrigger(FullName, RerunFlag ? "RERUN" : "START");
            break;
         case jsSuccess :
            if ( TriggerStartFlag )
            {
               DoTrigger(FullName, RerunFlag ? "RERUN" : "START", "SUCCESS");
            }
            else
            {
               DoTrigger(FullName, "SUCCESS");
            }
            break;
         case jsFail :
            if ( TriggerStartFlag )
            {
               if ( AutoSkip )
               {
                  DoTrigger(FullName, RerunFlag ? "RERUN" : "START", "FAIL", "SKIP");
               }
               else
               {
                  DoTrigger(FullName, RerunFlag ? "RERUN" : "START", "FAIL");
               }
            }
            else
            {
               if ( AutoSkip )
               {
                  DoTrigger(FullName, "FAIL", "SKIP");
               }
               else
               {
                  DoTrigger(FullName, "FAIL");
               }
            }
            break;
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0008);

   if ( TriggerFlag && Status == jsSkipped && !AutoSkip )
   {
      if ( OldStatus == jsUnknown )
      {
         DoTrigger(FullName, "FAIL", "SKIP");
      }
      else
      {
         DoTrigger(FullName, "SKIP");
      }
   }

   if ( Status == jsFail && !AutoSkip && CurrTime == NULL &&
        ( Job.Type == jtJob || Job.Type == jtEventWait || Job.Type == jtEventFire ||
          Job.Type == jtTime || Job.Type == jtBatch ) )
   {
      snprintf(ErrMsg, 2048, "Job '%s' fail", FullName.c_str());
      PushAlarmObj("JOB_FAIL", ErrMsg, Job);
   }

   SET_ACTION_POINT(ChangeStatus, 0x0009);

   if ( Status == jsRunning && Pid == 0 && ServerId != 88888 )
   {
      if ( PartFlag )
      {
         Part->ServerId = ServerId;
         PartXml->SetProperty("SERVER_ID", ServerId + 1);
         Part->Pid = 0;
         PartXml->SetProperty("PID", "0");
         Part->RegionId = RegionId;
      }
      else
      {
         Job.ServerId = ServerId;
         Job.Xml->SetProperty("SERVER_ID", ServerId + 1);
         Job.Pid = 0;
         Job.Xml->SetProperty("PID", "0");
         Job.RegionId = RegionId;
      }
      {
         bool Found = false;
         for ( group_info *GroupPtr = Job.GroupPtr; GroupPtr != NULL; )
         {
            if ( SectionSet.find(GroupPtr->Name) != SectionSet.end() )
            {
               Found = true;
               CurrentSection = GroupPtr->Name;
               SectionSet.erase(GroupPtr->Name);
            }
            if ( GroupPtr->AgentPtr == NULL )
            {
               GroupPtr = NULL;
            }
            else
            {
               GroupPtr = GroupPtr->AgentPtr->GroupPtr;
            }
         }
         if ( SectionSet.find(JobName) != SectionSet.end() )
         {
            Found = true;
            CurrentSection = JobName;
            SectionSet.erase(JobName);
         }
         if ( Found && CurrTime == NULL && ParaMonFile != NULL )
         {
            char TimeStr[200 + 1];
            BkCurrTime(TimeStr);
            fprintf(ParaMonFile, "%s,---\n", TimeStr);
            fflush(ParaMonFile);
         }
      }
   }

   if ( Status == jsUnknown )
   {
      snprintf(ErrMsg, 2048, "Job %s unknown", FullName.c_str());
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
   }

   SET_ACTION_POINT(ChangeStatus, 0x000a);

   if ( OldStatus == jsRunning && Status == jsWaiting && CurrTime == NULL )
   {
      if ( PartFlag )
      {
         int ServerId = Part->ServerId;
         if ( ServerId != MyId )
         {
            ClusterNodeVec[ServerId].ParaJobNum = 0;
            ClusterNodeVec[ServerId].SignonFlag = false;
         }
      }
      else
      {
         int ServerId = Job.ServerId;
         if ( ServerId != MyId )
         {
            ClusterNodeVec[ServerId].ParaJobNum = 0;
            ClusterNodeVec[ServerId].SignonFlag = false;
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x000b);

   if ( CurrTime == NULL )
   {
      HJobEvent Event;
      memset(&Event, 0, sizeof(Event));
      BkStrNCpy(Event.JobName, JobName.c_str(), sizeof(Event.JobName) - 1);
      Event.PartIdx = PartIdx;
      Event.Status = Status;
      Event.ServerId = ServerId;
      Event.RegionId = RegionId;
      if ( Status == jsRunning || Status == jsSuccess || Status == jsFail )
      {
         Event.Pid = Pid;
      }
      BkCurrTime(Event.Time);
      memcpy(Event.EyeCatcher, "SIMBAT\0\0", 8);
      if ( Job.Type != jtExternJob && Job.Type != jtGroup && !PartJobFlag )
      {
         HEventLogger::Put(Event);
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x000c);
   Job.LastStatusChangeTime = StatusChangeTime;

   if ( PartFlag )
   {
      Part->LastStatusChangeTime = StatusChangeTime;
      Part->SlaveNotSignon = false;
   }
   else
   {
      Job.SlaveNotSignon = false;
   }

   if ( Status == OldStatus ) // running step2(with pid) && running step1 or step
   {
      return;
   }

   SET_ACTION_POINT(ChangeStatus, 0x000d);

   if ( Job.Type != jtGroup && !PartJobFlag && !RestartFlag )
   {
      if ( Status == jsFail )
      {
         if ( !PartFlag )
         {
            Job.IdleTimeBegin = StatusChangeTime;
         }
      }
      if ( Status == jsRunning || Status == jsSkipped )
      {
         if ( !PartFlag )
         {
            if ( Job.IdleTimeBegin > 0 )
            {
               Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
               Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
               Job.IdleTimeBegin = 0;
            }
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x000e);

   group_info &Group = GroupMap[Job.GroupId];

   SET_ACTION_POINT(ChangeStatus, 0x000f);

   if ( Job.BusinessType != "" && BusinessTrafficControlMap.find(Job.BusinessType) != BusinessTrafficControlMap.end() )
   {
      HBusinessTrafficControl &Control = BusinessTrafficControlMap[Job.BusinessType];
      if ( OldStatus == jsRunning )
      {
         Control.RunningJobNum--;
      }
      if ( Status == jsRunning )
      {
         Control.RunningJobNum++;
      }
   }

   if ( PartFlag )
   {
      if ( OldStatus == jsWaiting )
      {
         Job.NumOfWaitingPart--;
      }
      if ( OldStatus == jsRunning || OldStatus == jsUnknown )
      {
         ClusterNodeVec[Part->ServerId].RunningJobNum--;
         if ( OldStatus == jsRunning )
         {
            ClusterNodeVec[Part->ServerId].RegionJobNumVec[Part->RegionId]--;
         }
         if ( Job.Type == jtEventWait || Job.Type == jtTime )
         {
            ClusterNodeVec[Part->ServerId].RunningWaitJobNum--;
         }
      }
      if ( OldStatus == jsRunning )
      {
         if ( Job.NumOfRunningPart == 1 )
         {
            Job.IdleTimeBegin = StatusChangeTime;
         }
         Job.NumOfRunningPart--;
         RunningJobNum--;
         GroupSubJobNum(Job.GroupPtr, Job.Type == jtEventWait || Job.Type == jtTime, StatusChangeTime);
      }
      if ( OldStatus == jsSuccess || OldStatus == jsSkipped )
      {
         Job.NumOfCompletedPart--;
      }
      if ( OldStatus == jsFail || OldStatus == jsUnknown )
      {
         Job.NumOfFailedPart--;
         if ( OldStatus == jsUnknown )
         {
            Job.NumOfUnknownPart--;
         }
      }
   }
   else
   {
      if ( Job.Type != jtExternJob && Job.Type != jtGroup && !PartJobFlag )
      {
         if ( JobOldStatus == jsRunning || JobOldStatus == jsUnknown )
         {
            ClusterNodeVec[Job.ServerId].RunningJobNum--;
            if ( OldStatus == jsRunning )
            {
               ClusterNodeVec[Job.ServerId].RegionJobNumVec[Job.RegionId]--;
            }
            if ( Job.Type == jtEventWait || Job.Type == jtTime )
            {
               ClusterNodeVec[Job.ServerId].RunningWaitJobNum--;
            }
         }
         if ( JobOldStatus == jsRunning )
         {
            RunningJobNum--;
            GroupSubJobNum(Job.GroupPtr, Job.Type == jtEventWait || Job.Type == jtTime, StatusChangeTime);
         }
      }
      if ( JobOldStatus == jsWaiting &&
           ( Job.AlarmTimeType == attAtTheMoment ||
             Job.AlarmTimeType == attDurationFromTheBatchStarting ) )
      {
         if ( JobAlarmMap.find(Job.Name) != JobAlarmMap.end() )
         {
            JobAlarmMap.erase(Job.Name);
         }
         Job.AlarmedFlag = false;
      }
      if ( ( Status == jsSuccess || Status == jsSkipped ) &&
           Job.AlarmTimeType == attDurationFromTheJobStarting )
      {
         if ( JobAlarmMap.find(Job.Name) != JobAlarmMap.end() )
         {
            JobAlarmMap.erase(Job.Name);
         }
         Job.AlarmedFlag = false;
      }
      if ( Status == jsRunning &&
           Job.AlarmTimeType == attDurationFromTheJobStarting && !Job.AlarmedFlag )
      {
         const char *s = Job.AlarmTime.c_str();
         char w[80 + 1];
         int Pos = 0;
         int Hour;
         int Min;
         Pos += ReadWord(s + Pos, w, " :", 80);
         Hour = StrToInt(w);
         Pos += ReadWord(s + Pos, w, " :", 80);
         Pos += ReadWord(s + Pos, w, " :", 80);
         Min = StrToInt(w);
         time_t Time = time(NULL) + Hour * 60 * 60 + Min * 60;
         JobAlarmMap[Job.Name] = Time;
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0010);

   if ( Status == jsRunning || Status == jsUnknown )
   {
      if ( PartFlag )
      {
         ClusterNodeVec[Part->ServerId].RunningJobNum++;
         if ( Status == jsRunning )
         {
            ClusterNodeVec[Part->ServerId].RegionJobNumVec[Part->RegionId]++;
         }
         if ( Job.Type == jtEventWait || Job.Type == jtTime )
         {
            ClusterNodeVec[Part->ServerId].RunningWaitJobNum++;
         }
      }
      else
      {
         if ( Job.Type != jtExternJob && Job.Type != jtGroup && !PartJobFlag )
         {
            ClusterNodeVec[Job.ServerId].RunningJobNum++;
            if ( Status == jsRunning )
            {
               ClusterNodeVec[Job.ServerId].RegionJobNumVec[Job.RegionId]++;
            }
            if ( Job.Type == jtEventWait || Job.Type == jtTime )
            {
               ClusterNodeVec[Job.ServerId].RunningWaitJobNum++;
            }
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0011);

   if ( OldStatus == jsWaiting && Status == jsRunning && !RestartFlag )
   {
      if ( Job.Type == jtGroup )
      {
         group_info &Group = GroupMap[Job.Group];
         if ( Group.RunningJobQty == 1 && Group.CompletedJobQty == 0 && Group.FailedJobQty == 0 )
         {
            Job.StartTime = StatusChangeTime;
            Job.Xml->SetProperty("START_TIME", Time);
            if ( Job.BeginningTime == 0 )
            {
               Job.BeginningTime = Job.StartTime;
               Job.Xml->SetProperty("BEGINNING_TIME", Time);
            }
         }
         Job.EndTime = 0;
         Job.Xml->SetProperty("END_TIME", "");
      }
      else
      {
         if ( PartFlag )
         {
            Part->StartTime = StatusChangeTime;
            PartXml->SetProperty("START_TIME", Time);
            if ( Part->BeginningTime == 0 )
            {
               Part->BeginningTime = Part->StartTime;
               PartXml->SetProperty("BEGINNING_TIME", Time);
            }
            Part->EndTime = 0;
            PartXml->SetProperty("END_TIME", "");
         }
         else
         {
            if ( Job.PartFlag )
            {
               if ( Job.NumOfRunningPart == 1 && Job.NumOfCompletedPart == 0 &&
                    Job.NumOfFailedPart == 0 )
               {
                  Job.StartTime = StatusChangeTime;
                  Job.Xml->SetProperty("START_TIME", Time);
                  if ( Job.BeginningTime == 0 )
                  {
                     Job.BeginningTime = Job.StartTime;
                     Job.Xml->SetProperty("BEGINNING_TIME", Time);
                  }
               }
               Job.EndTime = 0;
               Job.Xml->SetProperty("END_TIME", "");
            }
            else
            {
               Job.StartTime = StatusChangeTime;
               Job.Xml->SetProperty("START_TIME", Time);
               if ( Job.BeginningTime == 0 )
               {
                  Job.BeginningTime = Job.StartTime;
                  Job.Xml->SetProperty("BEGINNING_TIME", Time);
               }
               Job.Xml->SetProperty("END_TIME", "");
            }
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0012);

   if ( Status == jsRunning )
   {
      if ( PartFlag )
      {
         Part->LastUserStopTime = 0;
         RunningJobNum++;
         GroupAddJobNum(Job.GroupPtr, Job.Type == jtEventWait || Job.Type == jtTime, StatusChangeTime);
      }
      else
      {
         Job.LastUserStopTime = 0;
         if ( Job.Type != jtExternJob && Job.Type != jtGroup && !PartJobFlag )
         {
            RunningJobNum++;
            GroupAddJobNum(Job.GroupPtr, Job.Type == jtEventWait || Job.Type == jtTime, StatusChangeTime);
         }
      }
   }

   if ( ( Status == jsSuccess || Status == jsFail ||
          Status == jsUnknown || Status == jsSkipped ) && !RestartFlag )
   {
      if ( PartFlag )
      {
         Part->EndTime = StatusChangeTime;
         PartXml->SetProperty("END_TIME", Time);
      }
      else
      {
         if ( Job.Type == jtGroup || PartJobFlag )
         {
            if ( Status == jsSuccess )
            {
               if ( Job.IdleTimeBegin > 0 )
               {
                  Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
                  Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
                  Job.IdleTimeBegin = StatusChangeTime;
               }
               Job.EndTime = StatusChangeTime;
               Job.Xml->SetProperty("END_TIME", Time);
            }
         }
         else
         {
            if ( Status == jsSkipped && Job.IdleTimeBegin > 0 )
            {
               Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
               Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
               Job.IdleTimeBegin = 0;
            }
            Job.EndTime = StatusChangeTime;
            Job.Xml->SetProperty("END_TIME", Time);
         }
         if ( ( Status == jsSuccess || Status == jsSkipped ) && CurrTime == NULL )
         {
            HHisInfo Info;
            Info.JobName = Job.Name;
            Info.StartTime = TimeToString(Job.BeginningTime);
            Info.EndTime = TimeToString(Job.EndTime);
            Info.Status = Status == jsSuccess ? "SUCCESS" : "SKIPPED";
            Info.Elapse = Job.EndTime - Job.BeginningTime - Job.IdleTimeSum;
            WriteHisThread->PushBack(Info);
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0013);

   if ( PartFlag )
   {
      if ( Status == jsWaiting )
      {
         Job.NumOfWaitingPart++;
      }
      if ( Status == jsRunning )
      {
         Job.NumOfRunningPart++;
         if ( Job.IdleTimeBegin > 0 && !RestartFlag )
         {
            Job.IdleTimeSum += StatusChangeTime - Job.IdleTimeBegin;
            Job.Xml->SetProperty("IDLE_TIME_SUM", Job.IdleTimeSum);
            Job.IdleTimeBegin = 0;
         }
      }
      if ( Status == jsSuccess || Status == jsSkipped )
      {
         Job.NumOfCompletedPart++;
      }
      if ( Status == jsFail || Status == jsUnknown )
      {
         Job.NumOfFailedPart++;
         if ( Status == jsUnknown )
         {
            Job.NumOfUnknownPart++;
         }
      }
   }
   else
   {
      if ( Status == jsSuccess || Status == jsSkipped )
      {
         for ( int i = 0; i < Job.SuccJobs.size(); i++ )
            Graph[Job.SuccJobs[i]].InDegree--;
         CompletedJobNum++;
      }
      if ( JobOldStatus == jsSuccess || JobOldStatus == jsSkipped )
      {
         for ( int i = 0; i < Job.SuccJobs.size(); i++ )
            Graph[Job.SuccJobs[i]].InDegree++;
         CompletedJobNum--;
      }
   }

   if ( PartFlag )
   {
      Part->Status = Status;
      PartXml->SetProperty("STATUS", Status);
   }
   else
   {
      Job.Status = Status;
      Job.Xml->SetProperty("STATUS", Status);
   }

   SET_ACTION_POINT(ChangeStatus, 0x0014);

   if ( Job.Type == jtJob &&
        ( Status == jsFail || Status == jsUnknown ) &&
        Job.RestoreCommand != "" )
   {
      if ( PartFlag )
      {
         Part->RestoreStatus = rsNeedRestore;
         PartXml->SetProperty("RESTORE_STATUS", "NEED_RESTORE");
      }
      else
      {
         Job.RestoreStatus = rsNeedRestore;
         Job.Xml->SetProperty("RESTORE_STATUS", "NEED_RESTORE");
      }
   }

   if ( ( CurrTime == NULL || RebuildSummary ) &&
        ( Status == jsSuccess || Status == jsSkipped ||
          Status == jsFail || Status == jsUnknown ) )
      WriteSummary(Job, Status, PartFlag ? PartIdx : -1);

   if ( ( Status == jsSuccess || Status == jsSkipped ) && !PartFlag )
   {
      Job.StopAndSkipFlag = false;
   }

   SET_ACTION_POINT(ChangeStatus, 0x0015);

   if ( PartFlag )
   {
      HJobStatus NewStatus = jsNull;
      if ( Job.NumOfFailedPart > 0 )
      {
         if ( Job.NumOfFailedPart == Job.NumOfUnknownPart )
         {
            NewStatus = jsUnknown;
         }
         else
         {
            NewStatus = jsFail;
         }
      }
      else if ( Job.NumOfCompletedPart == Job.PartVec.size() )
      {
         NewStatus = jsSuccess;
         if ( TriggerFlag )
         {
            DoTrigger(JobName, "PART_END");
         }
      }
      else if ( Job.NumOfRunningPart > 0 || Job.NumOfCompletedPart > 0 )
      {
         NewStatus = jsRunning;
         if ( Job.NumOfRunningPart == 0 && Batch.Status == bsStopping )
         {
            NewStatus = jsSuccess;
         }
      }
      else
      {
         NewStatus = jsWaiting;
      }
      if ( NewStatus != jsNull && NewStatus != Job.Status )
      {
         ChangeStatus(Job.Name, NewStatus, 0, 0, 0, CurrTime);
      }
      return;
   }

   SET_ACTION_POINT(ChangeStatus, 0x0016);

   for ( int i = 0; i < Job.Alias.size(); i++ )
      ChangeStatus(Job.Alias[i], Status, ServerId, RegionId, Pid, CurrTime);

   SET_ACTION_POINT(ChangeStatus, 0x0017);

   if ( JobOldStatus == jsRunning )
   {
      Group.RunningJobQty--;
   }

   if ( JobOldStatus == jsSuccess || JobOldStatus == jsSkipped )
   {
      Group.CompletedJobQty--;
   }

   if ( JobOldStatus == jsFail || JobOldStatus == jsUnknown )
   {
      if ( Job.Type == jtExternJob )
      {
         Group.FailedExternJobQty--;
         if ( JobOldStatus == jsUnknown )
         {
            Group.UnknownExternJobQty--;
         }
      }
      else
      {
         Group.FailedJobQty--;
         if ( JobOldStatus == jsUnknown )
         {
            Group.UnknownJobQty--;
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0018);

   if ( Status == jsRunning )
   {
      Group.RunningJobQty++;
   }

   if ( Status == jsSuccess || Status == jsSkipped )
   {
      Group.CompletedJobQty++;
   }

   if ( Status == jsFail || Status == jsUnknown )
   {
      if ( Job.Type == jtExternJob )
      {
         Group.FailedExternJobQty++;
         if ( Status == jsUnknown )
         {
            Group.UnknownExternJobQty++;
         }
      }
      else
      {
         Group.FailedJobQty++;
         if ( Status == jsUnknown )
         {
            Group.UnknownJobQty++;
         }
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x0019);

   vector<HString> AgentVec;

   if ( Group.Agent != "" )
   {
      AgentVec.push_back(Group.Agent);
   }

   AgentVec.insert(AgentVec.end(), Group.ExternJobVec.begin(), Group.ExternJobVec.end());

   if ( AgentVec.empty() )
   {
      return;
   }

   SET_ACTION_POINT(ChangeStatus, 0x001a);

   for ( int i = 0; i < AgentVec.size(); i++ )
   {
      HString &Agent = AgentVec[i];
      if ( Group.FailedJobQty > 0 )
      {
         if ( Group.FailedJobQty == Group.UnknownJobQty )
         {
            ChangeStatus(Agent, jsUnknown, 0, 0, 0, CurrTime);
         }
         else
         {
            ChangeStatus(Agent, jsFail, 0, 0, 0, CurrTime);
         }
      }
      else if ( Group.CompletedJobQty == Group.JobQty )
      {
         ChangeStatus(Agent, jsSuccess, 0, 0, 0, CurrTime);
      }
      else if ( Group.RunningJobQty > 0 || Group.CompletedJobQty > 0 )
      {
         ChangeStatus(Agent, jsRunning, 0, 0, 0, CurrTime);
      }
      else
      {
         ChangeStatus(Agent, jsWaiting, 0, 0, 0, CurrTime);
      }
   }

   SET_ACTION_POINT(ChangeStatus, 0x001b);
}

static bool Load(const HString &BatName, bool Resume)
{
   SET_ACTION_POINT(Load, 0x0001);

   char TmpStr[500 + 1];
   char FileName[500 + 1];
   vector<HXmlTree*> Groups;
   HString SbtFileName;
   char ErrMsg[2048 + 1];

   if ( Resume )
   {
      snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());
      SbtFileName = FileName;
   }
   else
   {
      snprintf(FileName, 500, "%s/bats/%s.sbt", SimbatBase, BatName.c_str());
      SbtFileName = FileName;
      snprintf(FileName, 500, "bats/%s.sbt", BatName.c_str());
      PushBackupObj(HBackupObj(boOther, FileName));
      PushBackupObj(HBackupObj(boOther, "etc/http_post.ini"));
   }

   SET_ACTION_POINT(Load, 0x0002);
   snprintf(TmpStr, 500, "simbat_check %s", SbtFileName.c_str());
   int r = System.Run(TmpStr);

   if ( r == -1 || !WIFEXITED(r) || WEXITSTATUS(r) != 0 )
   {
      snprintf(ErrMsg, 2048, "Check %s fail", SbtFileName.c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   SET_ACTION_POINT(Load, 0x0003);

   if ( !SbtXml.LoadFromFile(SbtFileName.c_str()) )
   {
      snprintf(ErrMsg, 2048, "Load %s fail, %s", SbtFileName.c_str(), SbtXml.GetErrorString().c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   SET_ACTION_POINT(Load, 0x0004);
   JobTrigger = SbtXml.GetProperty("JOB_TRIGGER");
   BatchTrigger = SbtXml.GetProperty("BATCH_TRIGGER");
   AutoSkip = EnableAutoSkip && ( SbtXml.GetProperty("AUTO_SKIP") == "Y" );
   AcrossDayEnd = ( SbtXml.GetProperty("ACROSS_DAY_END") == "Y" );

   {
      HString AutoRerunStr = SbtXml.GetProperty("AUTO_RERUN", "0");
      int Pos = 0;
      char w[80 + 1];
      const char *s = AutoRerunStr.c_str();
      Pos += ReadWord(s + Pos, w, ", ", 80);
      AutoRerun = StrToInt(w);
      Pos += ReadWord(s + Pos, w, ", ", 80);
      Pos += ReadWord(s + Pos, w, ", ", 80);
      if ( w[0] == '\0' )
      {
         AutoRerunTime = 30;
      }
      else
      {
         AutoRerunTime = StrToInt(w);
      }
   }

   FadingPeriod = SbtXml.GetProperty("FADING_PERIOD");

   {
      vector<HString> StrVec;
      SplitString(SbtXml.GetProperty("PARTITION_DITHER").c_str(), ',', StrVec, true);
      if ( StrVec.size() == 2 )
      {
         PartitionDitherCount = StrToInt(StrVec[0].c_str());
         PartitionDitherStep = StrToInt(StrVec[1].c_str());
      }
   }

   SET_ACTION_POINT(Load, 0x0005);
   Banner = SbtXml.GetProperty("BANNER");

   {
      SectionSet.clear();
      vector<HString> StrVec;
      SplitString(SbtXml.GetProperty("SECTION_LIST"), ',', StrVec, true);
      for ( int i = 0; i < StrVec.size(); i++ )
      {
         HString &Str = StrVec[i];
         for ( int j = 0; j < Str.length(); j++ )
         {
            if ( Str[j] == ':' )
            {
               Str[j] = JobNameDelimiter[0];
            }
         }
         SectionSet.insert(Str);
      }
   }

   {
      BusinessTrafficControlMap.clear();
      HString TrafficControlStr = SbtXml.GetPropertyUpper("TRAFFIC_CONTROL");
      if ( TrafficControlStr != "" )
      {
         vector<HString> StrVec;
         SplitString(TrafficControlStr, ',', StrVec, true);
         for ( int i = 0; i < StrVec.size(); i++ )
         {
            const char *s = StrVec[i].c_str();
            int Pos = 0;
            char w[80 + 1];
            Pos += ReadWord(s + Pos, w, ": ", 80);
            HBusinessTrafficControl Control;
            Control.Type = w;
            Pos += ReadWord(s + Pos, w, ": ", 80);
            if ( w[0] != ':' )
            {
               snprintf(ErrMsg, 2048, "Traffic Control '%s' syntax error", TrafficControlStr.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
               continue;
            }
            Pos += ReadWord(s + Pos, w, ": ", 80);
            Control.ParaJobNum = StrToInt(w);
            BusinessTrafficControlMap[Control.Type] = Control;
         }
      }
   }

   if ( !Resume )
   {
      vector<HString> IncludeFiles;
      SbtXml.GetValues("INCLUDE_FILES", IncludeFiles);
      for ( int i = 0; i < IncludeFiles.size(); i++ )
      {
         snprintf(TmpStr, 500, "%s", IncludeFiles[i].c_str());
         BkRightTrim(TmpStr);
         BkToLower(TmpStr);
         snprintf(FileName, 500, "%s/bats/%s.sbt", SimbatBase, TmpStr);
         HXmlTree Xml(&SbtXml);
         if ( !Xml.LoadFromFile(FileName) )
         {
            snprintf(ErrMsg, 2048, "Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return false;
         }
         vector<HXmlTree*> Groups;
         Xml.GetChildren("GROUP", Groups);
         for ( int j = 0; j < Groups.size(); j++ )
         {
            Xml.MoveChildTo(Groups[j], &SbtXml);
         }
         snprintf(FileName, 500, "bats/%s.sbt", TmpStr);
         PushBackupObj(HBackupObj(boOther, FileName));
      }
      SbtXml.SetValues("INCLUDE_FILES", vector<HString>());
      SbtXml.GetChildren("GROUP", Groups);
      for ( int i = 0; i < Groups.size(); i++ )
      {
         vector<HXmlTree*> Jobs;
         Groups[i]->GetChildren("JOB", Jobs);
         if ( Jobs.empty() )
         {
            HXmlTree *DummyJob = new HXmlTree(Groups[i], "JOB");
            DummyJob->SetProperty("NAME", "DUMMY");
            DummyJob->SetProperty("TYPE", "JOB");
         }
      }
      snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());
      SbtXml.SaveToFile(FileName);
   }

   SET_ACTION_POINT(Load, 0x0006);
   SbtXml.SetProperty("BATCH_NAME", BatName);
   MainGroupName = "";
   Graph.clear();
   GroupMap.clear();
   ScheduleList.clear();
   RunningJobMap.clear();
   TotalJobNum = 0;
   SbtXml.GetChildren("GROUP", Groups);
   SET_ACTION_POINT(Load, 0x0007);

   for ( int i = 0; i < Groups.size(); i++ )
   {
      SET_ACTION_POINT(Load, 0x0107);
      group_info GroupInfo;
      vector<HXmlTree*> Jobs;
      GroupInfo.Name = Groups[i]->GetPropertyUpper("NAME");
      if ( i == 0 )
      {
         MainGroupName = GroupInfo.Name;
      }
      GroupInfo.JobQty = 0;
      GroupInfo.CompletedJobQty = 0;
      GroupInfo.RunningJobQty = 0;
      GroupInfo.FailedJobQty = 0;
      GroupInfo.FailedExternJobQty = 0;
      GroupInfo.UnknownJobQty = 0;
      GroupInfo.UnknownExternJobQty = 0;
      GroupInfo.Agent = "";
      GroupInfo.CondNot = false;
      GroupInfo.ConditionFile = "";
      GroupInfo.SpecialDate = "";
      GroupInfo.ConditionDateCache = 1;
      GroupInfo.ConditionFileCache = 1;
      GroupMap[GroupInfo.Name] = GroupInfo;
      Groups[i]->GetChildren("JOB", Jobs);
      SET_ACTION_POINT(Load, 0x0207);
      for ( int j = 0; j < Jobs.size(); j++ )
      {
         SET_ACTION_POINT(Load, 0x0307);
         job_info JobInfo;
         JobInfo.Name = GroupInfo.Name + JobNameDelimiter + Jobs[j]->GetPropertyUpper("NAME");
         JobInfo.GroupId = GroupInfo.Name;
         JobInfo.Type = StringToJobType(Jobs[j]->GetPropertyUpper("TYPE"));
         SET_ACTION_POINT(Load, 0x0407);
         if ( JobInfo.Type == jtJob )
         {
            SET_ACTION_POINT(Load, 0x0507);
            JobInfo.Command = Jobs[j]->GetProperty("COMMAND");
            JobInfo.RestoreCommand = Jobs[j]->GetProperty("RESTORE_COMMAND");
            JobInfo.AdditionalLog = Jobs[j]->GetProperty("ADDITIONAL_LOG");
            JobInfo.AlarmTimeType = StringToAlarmTimeType(Jobs[j]->GetProperty("ALARM_TIME_TYPE"));
            JobInfo.AlarmTime = Jobs[j]->GetPropertyEnv("ALARM_TIME");
            JobInfo.LoopStartTimeStr = Jobs[j]->GetProperty("LOOP_START_TIME");
            JobInfo.LoopEndTimeStr = Jobs[j]->GetProperty("LOOP_END_TIME");
            JobInfo.LoopInterval = StrToInt(Jobs[j]->GetProperty("LOOP_INTERVAL").c_str());
            JobInfo.LoopCount = StrToInt(Jobs[j]->GetProperty("LOOP_COUNT").c_str());
            if ( JobInfo.LoopCount <= 0 )
            {
               JobInfo.LoopCount = 3000;
            }
            JobInfo.LoopConditionVariable = Jobs[j]->GetProperty("LOOP_CONDITION_VARIABLE");
            SET_ACTION_POINT(Load, 0x0607);
         }
         else if ( JobInfo.Type == jtEventFire )
         {
            SET_ACTION_POINT(Load, 0x0707);
            HString Event = Jobs[j]->GetPropertyUpper("EVENT");
            if ( JobInfo.PartitionFile != "" )
            {
               Event = Event + "_$SIMBAT_PART";
            }
            JobInfo.Command = HString("simbat_evnt fire \"") +
               Jobs[j]->GetProperty("HOST_NAME") + "\" \"" +
               Event + "\"";
            SET_ACTION_POINT(Load, 0x0807);
         }
         else if ( JobInfo.Type == jtEventWait )
         {
            SET_ACTION_POINT(Load, 0x0907);
            HString Event = Jobs[j]->GetPropertyUpper("EVENT");
            if ( JobInfo.PartitionFile != "" )
            {
               Event = Event + "_$SIMBAT_PART";
            }
            HString File = Jobs[j]->GetProperty("WAIT_FOR_FILE");
            HString Time = Jobs[j]->GetProperty("WAIT_FOR_TIME");
            HString Command = Jobs[j]->GetProperty("WAIT_FOR_COMMAND");
            SET_ACTION_POINT(Load, 0x0a07);
            if ( strchr(Time.c_str(), ':') != NULL )
            {
               const char *s = Time.c_str();
               char w[80 + 1];
               int Pos = 0;
               int Hour;
               int Min;
               Pos += ReadWord(s + Pos, w, " :", 80);
               Hour = StrToInt(w);
               Pos += ReadWord(s + Pos, w, " :", 80);
               Pos += ReadWord(s + Pos, w, " :", 80);
               Min = StrToInt(w);
               int Delta = 0;
               snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
               if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
               {
                  Delta = 24 * 60 * 60;
               }
               Time = TimeToString(StringToTime(TmpStr) + Delta) + ":";
            }
            SET_ACTION_POINT(Load, 0x0b07);
            JobInfo.Command = HString("simbat_evnt wait") +
               " \"" + Event + "\"" +
               " \"" + File + "\"" +
               " \"" + Time + "\"" +
               " '" + ToShellArg(Command) + "'";
            SET_ACTION_POINT(Load, 0x0c07);
         }
         else if ( JobInfo.Type == jtTime )
         {
            SET_ACTION_POINT(Load, 0x0d07);
            HString Time = Jobs[j]->GetProperty("TIME");
            {
               const char *s = Time.c_str();
               char w[80 + 1];
               int Pos = 0;
               int Hour;
               int Min;
               Pos += ReadWord(s + Pos, w, " :", 80);
               Hour = StrToInt(w);
               Pos += ReadWord(s + Pos, w, " :", 80);
               Pos += ReadWord(s + Pos, w, " :", 80);
               Min = StrToInt(w);
               int Delta = 0;
               snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
               if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
               {
                  Delta = 24 * 60 * 60;
               }
               Time = TimeToString(StringToTime(TmpStr) + Delta) + ":";
            }
            SET_ACTION_POINT(Load, 0x0e07);
            JobInfo.Command = HString("simbat_evnt wait '' '' \"") + Time + "\" ''";
            SET_ACTION_POINT(Load, 0x0f07);
         }
         else
            JobInfo.Command = "";
         SET_ACTION_POINT(Load, 0x1307);
         if ( JobInfo.Type == jtGroup || JobInfo.Type == jtJob ||
              JobInfo.Type == jtEventWait || JobInfo.Type == jtEventFire ||
              JobInfo.Type == jtTime || JobInfo.Type == jtBatch )
         {
            JobInfo.SpecialDate = Jobs[j]->GetProperty("SPECIAL_DATE");
         }
         else
         {
            JobInfo.SpecialDate = "";
         }
         if ( JobInfo.Type == jtExternJob )
         {
            JobInfo.ExternJob = Jobs[j]->GetPropertyUpper("EXTERN_GROUP");
            HString Name = Jobs[j]->GetPropertyUpper("EXTERN_JOB");
            if ( Name != "" )
            {
               JobInfo.ExternJob = JobInfo.ExternJob + JobNameDelimiter + Name;
            }
         }
         else
            JobInfo.ExternJob = "";
         SET_ACTION_POINT(Load, 0x1407);
         {
            JobInfo.ExternJobList.clear();
            vector<HString> StrVec;
            Jobs[j]->GetValues("EXTERN_JOB_LIST", StrVec);
            for ( int k = 0; k < StrVec.size(); k++ )
            {
               const char *s = StrVec[k].c_str();
               char w[500 + 1];
               int Pos = 0;
               Pos += ReadWord(s + Pos, w, " :", 500);
               ToUpper(w);
               HString ExternGroup = w;
               Pos += ReadWord(s + Pos, w, " :", 500);
               if ( w[0] == ':' )
               {
                  Pos += ReadWord(s + Pos, w, " :", 500);
                  ToUpper(w);
                  HString ExternJob = w;
                  JobInfo.ExternJobList.push_back(ExternGroup + JobNameDelimiter + ExternJob);
               }
               else
               {
                  JobInfo.ExternJobList.push_back(ExternGroup);
               }
            }
         }
         SET_ACTION_POINT(Load, 0x1507);
         if ( JobInfo.Type == jtGroup )
         {
            JobInfo.Group = Jobs[j]->GetProperty("GROUP");
         }
         else
         {
            JobInfo.Group = "";
         }
         JobInfo.ParaJobNum = 0;
#ifdef SIMBAT_ENTERPRISE
         if ( !Load_aux(Jobs[j], JobInfo) )
         {
            return false;
         }
#endif
         if ( JobInfo.Type == jtEventWait )
            JobInfo.WaitForOperator = Jobs[j]->GetProperty("WAIT_FOR_OPERATOR") == "Y";
         else
            JobInfo.WaitForOperator = false;
         if ( JobInfo.Type == jtJob )
            JobInfo.Anchor = Jobs[j]->GetProperty("ANCHOR") == "Y";
         else
         {
            if ( JobInfo.Type == jtEventWait || JobInfo.Type == jtEventFire || JobInfo.Type == jtTime || JobInfo.Type == jtBatch )
               JobInfo.Anchor = true;
            else
               JobInfo.Anchor = false;
         }
         SET_ACTION_POINT(Load, 0x1607);
         if ( JobInfo.Type == jtGroup || JobInfo.Type == jtJob ||
             JobInfo.Type == jtEventWait || JobInfo.Type == jtEventFire ||
             JobInfo.Type == jtTime || JobInfo.Type == jtBatch )
         {
            JobInfo.CondNot = Jobs[j]->GetProperty("COND_NOT") == "Y";
            JobInfo.ConditionFile = Jobs[j]->GetProperty("CONDITION_FILE");
            JobInfo.Priority = StrToInt(Jobs[j]->GetProperty("PRIORITY", "0").c_str());
            JobInfo.Tag = Jobs[j]->GetProperty("TAG");
            HString AutoRerunStr = Jobs[j]->GetProperty("AUTO_RERUN", "0");
            int Pos = 0;
            char w[80 + 1];
            const char *s = AutoRerunStr.c_str();
            Pos += ReadWord(s + Pos, w, ", ", 80);
            JobInfo.AutoRerun = StrToInt(w);
            Pos += ReadWord(s + Pos, w, ", ", 80);
            Pos += ReadWord(s + Pos, w, ", ", 80);
            if ( w[0] == '\0' )
            {
               JobInfo.AutoRerunTime = 30;
            }
            else
            {
               JobInfo.AutoRerunTime = StrToInt(w);
            }
         }
         else
         {
            JobInfo.CondNot = false;
            JobInfo.ConditionFile = "";
            JobInfo.Tag = "";
            JobInfo.Priority = 0;
            JobInfo.AutoRerun = 0;
         }
         SET_ACTION_POINT(Load, 0x1707);
         if ( JobInfo.Type == jtJob || JobInfo.Type == jtEventWait ||
              JobInfo.Type == jtEventFire )
         {
            JobInfo.JobTrigger = Jobs[j]->GetProperty("JOB_TRIGGER");
            JobInfo.Exclusive = Jobs[j]->GetProperty("EXCLUSIVE") == "Y";
         }
         else
         {
            JobInfo.JobTrigger = "";
            JobInfo.PartitionFile = "";
            JobInfo.Exclusive = false;
         }
         SET_ACTION_POINT(Load, 0x1807);
         JobInfo.Status = jsWaiting;
         JobInfo.RestoreStatus = rsNull;
         Jobs[j]->SetProperty("STATUS", jsWaiting);
         Jobs[j]->SetProperty("RESTORE_STATUS", "");
         Jobs[j]->SetProperty("BEGINNING_TIME", "");
         Jobs[j]->SetProperty("START_TIME", "");
         Jobs[j]->SetProperty("END_TIME", "");
         Jobs[j]->SetProperty("IDLE_TIME_SUM", "0");
         JobInfo.SuccJobs.clear();
         JobInfo.Alias.clear();
         JobInfo.InDegree = 0;
         JobInfo.Xml = Jobs[j];
         JobInfo.Pid = 0;
         JobInfo.RunningTimes = 0;
         JobInfo.LastTriggerPid = 0;
         SET_ACTION_POINT(Load, 0x1907);
         Graph[JobInfo.Name] = JobInfo;
         SET_ACTION_POINT(Load, 0x1a07);
         TotalJobNum++;
      }
      SET_ACTION_POINT(Load, 0x1b07);
      vector<HXmlTree*> Links;
      Groups[i]->GetChildren("LINK", Links);
      for ( int j = 0; j < Links.size(); j++ )
      {
         HString StartJob;
         HString EndJob;
         StartJob = GroupInfo.Name + JobNameDelimiter +
            Links[j]->GetPropertyUpper("START_JOB");
         EndJob = GroupInfo.Name + JobNameDelimiter + Links[j]->GetPropertyUpper("END_JOB");
         Graph[EndJob].InDegree++;
         if ( Graph[StartJob].Type != jtExternJob )
         {
            Graph[EndJob].InDegreeWithinGroup++;
         }
         Graph[StartJob].SuccJobs.push_back(EndJob);
      }
      SET_ACTION_POINT(Load, 0x1c07);
   }

   SET_ACTION_POINT(Load, 0x0008);

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.Type == jtExternJob )
      {
         if ( strstr(Job.ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            Graph[Job.ExternJob].Alias.push_back(Job.Name);
         }
         else
         {
            group_info &Group = GroupMap[Job.ExternJob];
            Group.ExternJobVec.push_back(Job.Name);
         }
      }
      GroupMap[Job.GroupId].JobQty++;
      if ( Job.Type == jtGroup )
      {
         group_info &Group = GroupMap[Job.Group];
         Group.Agent = Iter->first;
         Group.SpecialDate = Job.SpecialDate;
         Group.CondNot = Job.CondNot;
         Group.ConditionFile = Job.ConditionFile;
         if ( Job.SpecialDate != "" )
         {
            Group.ConditionDateCache = 2;
         }
         if ( Job.ConditionFile != "" )
         {
            Group.ConditionFileCache = 2;
         }
#ifdef SIMBAT_ENTERPRISE
         Group.ParaJobNum = Job.ParaJobNum;
#endif
      }
   }

   SET_ACTION_POINT(Load, 0x0009);

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      Job.Priority = AdjustPriority(Job.Name);
      Job.RunningGroupIdx = AdjustRunningGroupIdx(Job.Name);
      Job.BusinessType = AdjustBusinessType(Job.Name);
      HAutoRerunInfo Info = AdjustAutoRerun(Job.Name);
      Job.AutoRerun = Info.AutoRerun;
      Job.AutoRerunTime = Info.AutoRerunTime;
      for ( HString JobName = Job.Name; JobName != ""; )
      {
         group_info &Group = GroupMap[Graph[JobName].GroupId];
         Job.UpperGroupVec.push_back(Group.Name);
         if ( Group.ParaJobNum > 0 )
         {
            Job.GroupTrafficControlFlag = true;
         }
         JobName = Group.Agent;
      }
   }

   SET_ACTION_POINT(Load, 0x000a);

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.GroupId != "" )
      {
         Job.GroupPtr = &GroupMap[Job.GroupId];
      }
   }

   SET_ACTION_POINT(Load, 0x000b);

   for ( map<HString, group_info>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      group_info &Group = Iter->second;
      if ( Group.Agent != "" )
      {
         Group.AgentPtr = &Graph[Group.Agent];
      }
   }

   SET_ACTION_POINT(Load, 0x000c);

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      for ( int i = 0; i < Job.ExternJobList.size(); i++ )
      {
         HString &ExternJob = Job.ExternJobList[i];
         Job.InDegree++;
         if ( strstr(ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            Graph[ExternJob].SuccJobs.push_back(Job.Name);
         }
         else
         {
            GroupMap[ExternJob].AgentPtr->SuccJobs.push_back(Job.Name);
         }
      }
   }

   SET_ACTION_POINT(Load, 0x000d);
   HCalcSection Calc(SimbatBase, JobNameDelimiter, SectionSet);

   if ( !Calc.Load(SbtFileName.c_str(), ErrMsg) )
   {
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   SET_ACTION_POINT(Load, 0x000e);
   Calc.Calc();
   SET_ACTION_POINT(Load, 0x000f);
   return true;
}

bool StartBatch(const char *BatName, HString &ErrStr, bool Resume, const char *StartDate, const char *NextDate)
{
   SET_ACTION_POINT(StartBatch, 0x0001);

   char TmpStr[500 + 1];
   char Command[8192 + 1];
   char ErrMsg[2048 + 1];

   if ( !Resume && Batch.Status != bsNotStarted && Batch.Status != bsStopped )
   {
      snprintf(ErrMsg, 2048, "Batch is running already");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ErrStr = ErrMsg;
      return false;
   }

   if ( Resume && Batch.Status == bsStopping )
   {
      Batch.Status = bsStopped;
   }

   HXmlTree Cfg;
   HXmlTree CfgCust;

   SET_ACTION_POINT(StartBatch, 0x0002);

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ErrStr = ErrMsg;
         Lock.Unlock();
         ChangeBatchStatus(bsNotStarted);
         return false;
      }
      LoadFromCfg(Cfg);
      if ( FileExist(ConfigCustFile.c_str()) )
      {
         if ( !CfgCust.LoadFromFile(ConfigCustFile.c_str()) )
         {
            snprintf(ErrMsg, 2048, "Load %s fail, %s", ConfigCustFile.c_str(),
               CfgCust.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ErrStr = ErrMsg;
            Lock.Unlock();
            ChangeBatchStatus(bsNotStarted);
            return false;
         }
         LoadFromCfgCust(CfgCust);
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0003);
   LoadVarpool();

   if ( !Resume )
   {
      System.Close();
      if ( !System.Init() )
      {
         exit(8);
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0004);

   char OldBatchTime[8 + 6 + 1];

   if ( Resume )
   {
      BkStrNCpy(LastBatchTime, Batch.StartTime, sizeof(LastBatchTime) - 1);
   }
   else
   {
      BkStrNCpy(OldBatchTime, LastBatchTime, sizeof(OldBatchTime) - 1);
      Batch = HBatch();
      BkCurrTime(Batch.StartTime);
      Batch.EndTime[0] = '\0';
      BkStrNCpy(Batch.Name, BatName, sizeof(Batch.Name) - 1);
      BkStrNCpy(LastBatchTime, Batch.StartTime, sizeof(LastBatchTime) - 1);
      if ( StartDate == NULL || strcmp(StartDate, NextScheduleDate.c_str()) == 0 )
      {
         if ( NextScheduleDate == "" )
         {
            BatchDate = HString(Batch.StartTime, 8);
         }
         else
         {
            BatchDate = NextScheduleDate;
         }
         NextBatchDate = CalcNextScheduleDate(BatchDate);
         NextScheduleDate = NextBatchDate;
      }
      else
      {
         BatchDate = StartDate;
         if ( NextDate == NULL || NextDate[0] == '\0' )
         {
            NextBatchDate = CalcNextScheduleDate(BatchDate);
         }
         else
         {
            NextBatchDate = NextDate;
         }
      }
      SetEnv("SIMBAT_DATE", BatchDate.c_str(), false);
      SetEnv("SIMBAT_NEXT_DATE", NextBatchDate.c_str(), false);
      SetEnv("SIMBAT_NEXT_SCHEDULE_DATE", NextScheduleDate.c_str(), false);
      SetEnv("SIMBAT_BATCH", BatName, false);
      CurrentSection = "";
      {
         time_t t = StringToTime(BatchDate);
         struct tm Tm;
         localtime_r(&t, &Tm);
         bool LeapFlag = false;
         int Year = Tm.tm_year + 1900;
         int Month = Tm.tm_mon + 1;
         int Day = Tm.tm_mday;
         if ( Year % 4 == 0 )
         {
            LeapFlag = true;
            if ( Year % 100 == 0 && Year % 400 != 0 )
            {
               LeapFlag = false;
            }
         }
         static int DaysArray[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
         int Days = DaysArray[Month - 1];
         if ( LeapFlag && Month == 2 )
         {
            Days++;
         }
         SetEnv("MONTH_BEGIN_IND", Day == 1 ? "Y" : "N", false);
         SetEnv("MONTH_END_IND", Day == Days ? "Y" : "N", false);
         SetEnv("QUARTER_BEGIN_IND", Month % 3 == 1 && Day == 1 ? "Y" : "N", false);
         SetEnv("QUARTER_END_IND", Month % 3 == 0 && Day == Days ? "Y" : "N", false);
         SetEnv("HALF_YEAR_BEGIN_IND", Month % 6 == 1 && Day == 1 ? "Y" : "N", false);
         SetEnv("HALF_YEAR_END_IND", Month % 6 == 0 && Day == Days ? "Y" : "N", false);
         SetEnv("YEAR_BEGIN_IND", Month == 1 && Day == 1 ? "Y" : "N", false);
         SetEnv("YEAR_END_IND", Month % 12 == 0 && Day == Days ? "Y" : "N", false);
      }
      if ( HotbackEnableFlag )
      {
         HotbackPausedFlag = false;
         {
            HXmlTree Cfg;
            HFileLockWrite Lock(ConfigLockFile.c_str());
            if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
            {
               snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               Cfg.SetValue("HOTBACK_PAUSED", "false");
               vector<HXmlTree*> NodeVec;
               Cfg.GetChildren("BACKUP", NodeVec);
               for ( int i = 0; i < NodeVec.size(); i++ )
               {
                  HXmlTree *Node = NodeVec[i];
                  Node->SetValue("PAUSED", "false");
               }
               Cfg.SaveToFile(ConfigFile.c_str());
            }
         }
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0005);

   if ( !Load(BatName, Resume) )
   {
      ChangeBatchStatus(bsNotStarted);
      snprintf(ErrMsg, 2048, "Load %s fail", BatName);
      ErrStr = ErrMsg;
      return false;
   }

   SET_ACTION_POINT(StartBatch, 0x0006);

   if ( !Resume )
   {
      BackupSummary(OldBatchTime, LastBatchTime);
      snprintf(Command, 8192, "rm -f %s/status/job_events 2> /dev/null", ShrDir.c_str());
      System.Run(Command);
      snprintf(Command, 8192, "cd %s/part_bak;rm -f * 2> /dev/null", ShrDir.c_str());
      System.Run(Command);
      if ( JobEventFd >= 0 )
      {
         close(JobEventFd);
      }
      char FileName[500 + 1];
      snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());
      JobEventFd  = open(FileName, O_CREAT|O_RDWR|O_TRUNC, 0666);
      if ( JobEventFd < 0 )
      {
         snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ChangeBatchStatus(bsNotStarted);
         return false;
      }
      HEventLogger::Init(JobEventFd, 0);
      if ( !HEventLogger::Alloc() )
      {
         ChangeBatchStatus(bsNotStarted);
         return false;
      }
      if ( ClearEventFlag )
      {
         snprintf(Command, 8192, "cd %s/events;rm -f * 2>/dev/null", ShrDir.c_str());
         System.Run(Command);
      }
      if ( ClearPartFlag )
      {
         snprintf(Command, 8192, "cd %s/part;rm -f * 2>/dev/null", ShrDir.c_str());
         System.Run(Command);
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0007);

   if ( BatchAlarmedFlag && AlarmBatchName == BatName )
   {
      BatchAlarmedFlag = false;
   }

   RunningJobNum = 0;
   CompletedJobNum = 0;
   BatchTimeTick = 0;
   JobAlarmMap.clear();
   BatchRestartFlag = false;

   {
      int NodeQty = ClusterNodeVec.size();
      for ( int i = 0; i < NodeQty; i++ )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[i];
         Node.RunningJobNum = 0;
         Node.RegionJobNumVec.clear();
         for ( int j = 0; j < NumOfRegion; j++ )
         {
            Node.RegionJobNumVec.push_back(0);
         }
         Node.RunningWaitJobNum = 0;
      }
   }

   if ( !Resume || RebuildSummary )
   {
      char FileName[500 + 1];
      snprintf(FileName, 500, "%s/log_%s/summary.txt", LogDir.c_str(), LastBatchTime);
      FILE *f = fopen(FileName, "wt");
      if ( f != NULL )
      {
         fprintf(f, "Batch start time : %.4s/%.2s/%.2s %.2s:%.2s:%.2s, Batch date : %s\n\n",
            Batch.StartTime, Batch.StartTime + 4, Batch.StartTime + 4 + 2,
            Batch.StartTime + 4 + 2 + 2, Batch.StartTime + 4 + 2 + 2 + 2,
            Batch.StartTime + 4 + 2 + 2 + 2 + 2, BatchDate.c_str());
         fprintf(f, "JOB NAME                                |"
            "START TIME         |END TIME           |ELAPSED TIME|STATUS    |SERVER ID |TAG\n");
         fclose(f);
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0008);

   if ( strcmp(Batch.Name, AlarmBatchName.c_str()) == 0 )
   {
      AlarmBatchStartTime = StringToTime(Batch.StartTime);
   }

   if ( Resume )
   {
      if ( Batch.Status == bsStarted || Batch.Status == bsPaused )
      {
         char FileName[500 + 1];
         snprintf(FileName, 500, "%s/etc/para_job_num.csv", ShrDir.c_str());
         ParaMonFile = fopen(FileName, "at");
         if ( ParaMonFile == NULL )
         {
            snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FileName, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
   }
   else
   {
      char FileName[500 + 1];
      snprintf(FileName, 500, "%s/etc/para_job_num.csv", ShrDir.c_str());
      ParaMonFile = fopen(FileName, "wt");
      if ( ParaMonFile == NULL )
      {
         snprintf(ErrMsg, 2048, "Can not create %s, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
      fprintf(ParaMonFile, "date_time,node_id,para_job_num\n");
      ChangeBatchStatus(bsStarted);
      DoBatchTrigger("START");
   }

   if ( Batch.Status != bsNotStarted )
   {
      time_t BatchStartTime = StringToTime(Batch.StartTime);
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( ( Job.Type != jtJob && Job.Type != jtBatch ) || Job.AlarmTime == "" )
         {
            continue;
         }
         const char *s = Job.AlarmTime.c_str();
         char w[80 + 1];
         int Pos = 0;
         int Hour;
         int Min;
         Pos += ReadWord(s + Pos, w, " :", 80);
         Hour = StrToInt(w);
         Pos += ReadWord(s + Pos, w, " :", 80);
         if ( strcmp(w, ":") != 0 )
         {
            snprintf(ErrMsg, 2048, "Job %s, alarm time '%s' syntax error", Job.Name.c_str(), Job.AlarmTime.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            continue;
         }
         Pos += ReadWord(s + Pos, w, " :", 80);
         Min = StrToInt(w);
         if ( Job.AlarmTimeType == attAtTheMoment )
         {
            int Delta = 0;
            snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
            if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
            {
               Delta = 24 * 60 * 60;
            }
            JobAlarmMap[Job.Name] = StringToTime(TmpStr) + Delta;
         }
         else if ( Job.AlarmTimeType == attDurationFromTheBatchStarting )
         {
            JobAlarmMap[Job.Name] = BatchStartTime + ( Hour * 60 + Min ) * 60;
         }
      }
   }

   SET_ACTION_POINT(StartBatch, 0x0009);

   if ( Batch.Status != bsNotStarted )
   {
      time_t BatchStartTime = StringToTime(Batch.StartTime);
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.Type != jtJob )
         {
            continue;
         }
         Job.LoopStartTime = StringToTime(Batch.StartTime);
         if ( Job.LoopStartTimeStr != "" )
         {
            int Delta = 0;
            const char *s = Job.LoopStartTimeStr.c_str();
            char w[80 + 1];
            int Pos = 0;
            int Hour;
            int Min;
            Pos += ReadWord(s + Pos, w, " :", 80);
            Hour = StrToInt(w);
            Pos += ReadWord(s + Pos, w, " :", 80);
            if ( strcmp(w, ":") != 0 )
            {
               snprintf(ErrMsg, 2048, "Job %s, loop start time '%s' syntax error", Job.Name.c_str(), Job.LoopStartTimeStr.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
               continue;
            }
            Pos += ReadWord(s + Pos, w, " :", 80);
            Min = StrToInt(w);
            snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
            if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
            {
               Delta = 24 * 60 * 60;
            }
            Job.LoopStartTime = StringToTime(TmpStr) + Delta;
         }
         if ( Job.LoopEndTimeStr != "" )
         {
            int Delta = 0;
            const char *s = Job.LoopEndTimeStr.c_str();
            char w[80 + 1];
            int Pos = 0;
            int Hour;
            int Min;
            Pos += ReadWord(s + Pos, w, " :", 80);
            Hour = StrToInt(w);
            Pos += ReadWord(s + Pos, w, " :", 80);
            if ( strcmp(w, ":") != 0 )
            {
               snprintf(ErrMsg, 2048, "Job %s, loop end time '%s' syntax error", Job.Name.c_str(), Job.LoopEndTimeStr.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
               continue;
            }
            Pos += ReadWord(s + Pos, w, " :", 80);
            Min = StrToInt(w);
            snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
            if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
            {
               Delta = 24 * 60 * 60;
            }
            Job.LoopEndTime = StringToTime(TmpStr) + Delta;
         }
      }
   }

   gettimeofday(&LastScheduleTime, NULL);
   SET_ACTION_POINT(StartBatch, 0x000a);

   if ( !Resume )
   {
      BsWriteToSocket(SockDb, "B", 1, 0);
      LastHotbackFailTime = 0;
      InitBackupVec();
      AddVarpoolVersion();
      DoHotback();
      WriteHisThread->Init(SystemName, LastBatchTime, "NORMAL");
   }

   SET_ACTION_POINT(StartBatch, 0x000b);
   return true;
}

static void InitOrResumeBatch()
{
   char FileName[500 + 1];
   char ErrMsg[2048 + 1];

   Batch = HBatch();
   snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());
   FILE *f = fopen(FileName, "rb");

   if ( f == NULL )
   {
      return;
   }

   if ( fread(&Batch, sizeof(HBatch), 1, f) != 1 )
   {
      fclose(f);
      return;
   }

   fclose(f);

   if ( Batch.Name[0] == '\0' || Batch.Status == bsNotStarted )
   {
      return;
   }

   if ( Batch.Status == bsStarted )
   {
      Batch.Status = bsPaused;
   }

   BkStrNCpy(LastBatchTime, Batch.StartTime, sizeof(LastBatchTime) - 1);
   snprintf(FileName, 500, "%s/log_%s/summary.txt", LastBatchTime, ShrDir.c_str());

   if ( !FileExist(FileName) )
   {
      RebuildSummary = true;
      snprintf(ErrMsg, 2048, "Rebuilding summary.txt");
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
   }

   HString ErrStr;

   if ( !StartBatch(Batch.Name, ErrStr, true) )
   {
      RebuildSummary = false;
      return;
   }

   int RecNo = 0;

   snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());

   FILE *f_event = fopen(FileName, "rb");

   if ( f_event == NULL )
   {
      snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      exit(8);
   }

   snprintf(ErrMsg, 2048, "Reloading job_events begin");
   Syslog.Log(__FILE__, __LINE__, llInfo, "ENTRY", ErrMsg);

   char LastEventTime[80 + 1] = { 0x00 };

   for ( ; ; )
   {
      HJobEvent Event;
      int r = fread(&Event, sizeof(HJobEvent), 1, f_event);
      if ( r != 1 )
      {
         break;
      }
      if ( memcmp(Event.EyeCatcher, "EMPTY", 5) == 0 )
      {
         break;
      }
      RecNo++;
      if ( Graph.find(Event.JobName) == Graph.end() ||
           Event.Status < jsBEGIN || Event.Status > jsUnknown ||
           memcmp(Event.EyeCatcher, "SIMBAT", 6) != 0)
      {
         snprintf(ErrMsg, 2048, "%s damaged, RecNo = %d", FileName, RecNo);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         exit(8);
      }
      if ( RecNo % 100000 == 0 )
      {
         snprintf(ErrMsg, 2048, "%d records reloaded", RecNo);
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      }
      Event.ServerId = Event.ServerId % ClusterNodeVec.size();
      if ( Event.Time[0] != '\0' )
      {
         BkStrNCpy(LastEventTime, Event.Time, sizeof(LastEventTime) - 1);
      }
      job_info &Job = Graph[Event.JobName];
      if ( Event.NumOfPart > 0 )
      {
         if ( Job.PartFlag )
         {
            continue;
         }
         time_t StatusChangeTime = StringToTime(Event.Time);
         char PartitionFile[500 + 1];
         {
            HString FileName = EnvEvaluate(Job.PartitionFile);
            snprintf(PartitionFile, 500, "%s/part/", ShrDir.c_str());
            int Len = strlen(PartitionFile);
            if ( memcmp(FileName.c_str(), PartitionFile, Len) == 0 )
            {
               snprintf(PartitionFile, 500, "%s/part_bak/%s", ShrDir.c_str(), FileName.c_str() + Len);
            }
            else
            {
               snprintf(PartitionFile, 500, "%s", FileName.c_str());
            }
         }
         FILE *f = fopen(PartitionFile, "rt");
         if ( f == NULL )
         {
            snprintf(ErrMsg, 2048, "Open %s fail, errno = %d, RecNo = %d", PartitionFile, errno, RecNo);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            exit(8);
         }
         set<HString> PartSet;
         for ( ; ; )
         {
            char Line[500 + 1];
            char PartId[80 + 1];
            if ( ReadLine(f, Line, 500) == -1 )
            {
               break;
            }
            BkRightTrim(Line);
            if ( Line[0] == '#' || Line[0] == '\0' )
            {
               continue;
            }
            PartSet.insert(Line);
         }
         fclose(f);
         if ( PartSet.size() != Event.NumOfPart )
         {
            snprintf(ErrMsg, 2048, "Partition quantity %d not match file %s, RecNo = %d",
               Event.NumOfPart, Job.PartitionFile.c_str(), RecNo);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            exit(8);
         }
         Job.PartFlag = true;
         Job.PartitionStartTimeTick = BatchTimeTick++;
         Job.LastStatusChangeTime = StatusChangeTime;
         Job.ServerId = MyId;
         Job.PartVec.clear();
         int Idx = 0;
         for ( set<HString>::iterator Iter = PartSet.begin();
               Iter != PartSet.end(); Iter++ )
         {
            part_info Part;
            Part.Idx = Idx++;
            Part.Id = *Iter;
            Part.LastStatusChangeTime = Job.LastStatusChangeTime;
            HXmlTree *Xml = new HXmlTree(Job.Xml, "PART");
            Xml->SetProperty("ID", PartIdxString(Part.Idx).c_str());
            Xml->SetProperty("STATUS", jsWaiting);
            Part.Xml = Xml;
            Job.PartVec.push_back(Part);
         }
         Job.Xml->SetProperty("PART_FLAG", "Y");
         Job.NumOfWaitingPart = Event.NumOfPart;
      }
      else
      {
         HString FullName = Event.JobName;
         if ( Event.PartIdx != -1 )
         {
            time_t StatusChangeTime = StringToTime(Event.Time);
            ReloadStatus(Job, Event.PartIdx, Event.Status, Event.ServerId, Event.RegionId, Event.Pid, StatusChangeTime);
            HJobStatus NewStatus = jsNull;
            if ( Job.NumOfFailedPart > 0 )
            {
               if ( Job.NumOfFailedPart == Job.NumOfUnknownPart )
               {
                  NewStatus = jsUnknown;
               }
               else
               {
                  NewStatus = jsFail;
               }
            }
            else if ( Job.NumOfCompletedPart == Job.PartVec.size() )
            {
               NewStatus = jsSuccess;
            }
            else if ( Job.NumOfRunningPart > 0 || Job.NumOfCompletedPart > 0 )
            {
               NewStatus = jsRunning;
            }
            else
            {
               NewStatus = jsWaiting;
            }
            if ( NewStatus != jsNull && NewStatus != Job.Status )
            {
               ChangeStatus(Job.Name, NewStatus, 0, 0, 0, Event.Time);
            }
         }
         else
         {
            ChangeStatus(FullName, Event.Status, Event.ServerId, Event.RegionId, Event.Pid, Event.Time);
         }
      }
   }

   if ( strlen(LastEventTime) == 8 + 6 && Batch.Status == bsStopped )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.PartFlag && Job.NumOfRunningPart == 0 && Job.Status == jsRunning )
         {
            ChangeStatus(Job.Name, jsSuccess, 0, 0, 0, LastEventTime);
         }
      }
   }

   fclose(f_event);
   snprintf(ErrMsg, 2048, "Reloading job_events finished, total records = %d", RecNo);
   Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);

   long Offset = RecNo * sizeof(HJobEvent);

   truncate(FileName, Offset);
   JobEventFd  = open(FileName, O_CREAT|O_RDWR, 0666);

   if ( JobEventFd < 0 )
   {
      snprintf(ErrMsg, 2048, "open %s fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      exit(8);
   }

   lseek(JobEventFd, Offset, SEEK_SET);

   HEventLogger::Init(JobEventFd, RecNo);

   if ( RebuildSummary )
   {
      if ( Batch.Status == bsStopped )
      {
         SetBatchStopStatus();
      }
      RebuildSummary = false;
   }

   if ( Batch.Status != bsNotStarted )
   {
      if ( !HEventLogger::Alloc() )
      {
         exit(8);
      }
   }

   snprintf(ErrMsg, 2048, "Resetting data structures...");
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
        Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
           Job.Type != jtTime && Job.Type != jtBatch )
         continue;
      if ( Job.PartFlag )
      {
         for ( int i = 0; i < Job.PartVec.size(); i++ )
         {
            part_info &Part = Job.PartVec[i];
            HXmlTree *PartXml = Part.Xml;
            PartXml->SetProperty("BEGINNING_TIME", TimeToString(Part.BeginningTime));
            PartXml->SetProperty("START_TIME", TimeToString(Part.StartTime));
            PartXml->SetProperty("END_TIME", TimeToString(Part.EndTime));
            PartXml->SetProperty("SERVER_ID", Part.ServerId + 1);
            PartXml->SetProperty("PID", Part.Pid);
            PartXml->SetProperty("STATUS", Part.Status);
            if ( Part.RestoreStatus == rsNeedRestore )
            {
               PartXml->SetProperty("RESTORE_STATUS", "NEED_RESTORE");
            }
            if ( Part.Status != jsRunning && Part.Status != jsUnknown )
            {
               continue;
            }
            HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
            if ( Part.Status == jsRunning )
            {
               snprintf(ErrMsg, 2048, "Job %s unknown", FullName.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
               ChangeStatus(FullName, jsUnknown, 0, 0, 0);
            }
         }
      }
      else
      {
         if ( Job.Status != jsRunning && Job.Status != jsUnknown )
         {
            continue;
         }
         if ( Job.Status == jsRunning )
         {
            snprintf(ErrMsg, 2048, "Job %s unknown", Job.Name.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            ChangeStatus(Job.Name, jsUnknown, 0, 0, 0);
         }
      }
   }

   for ( map<HString, group_info>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      group_info &Group = Iter->second;
      if ( Group.Agent == "" )
      {
         continue;
      }
      job_info &Job = Graph[Group.Agent];
      Job.IdleTimeSum = 0;
      Job.Xml->SetProperty("IDLE_TIME_SUM", 0);
      Job.IdleTimeBegin = 0;
   }

   snprintf(ErrMsg, 2048, "Reset data structures completed");
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
}

static bool Init()
{
   SET_ACTION_POINT(Init, 0x0001);

   char FileName[500 + 1];
   char ErrMsg[2048 + 1];

   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      BkPrintError("Line %d : $SIMBAT_BASE not defined", __LINE__);
      return false;
   }

   SET_ACTION_POINT(Init, 0x0002);
   BkSignalIgnore(SIGPIPE);
   BkSignalIgnore(SIGCHLD);
   ParentPid = getppid();
   BkRandomize();
   ServerStartTime = time(NULL);
   LastActionTime = ServerStartTime;
   snprintf(BkTmpDir, 500, "%s/tmp", SimbatBase);
   chdir(BkTmpDir);
   snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
   ConfigFile = FileName;
   snprintf(FileName, 500, "%s/tmp/config.lck", SimbatBase);
   ConfigLockFile = FileName;
   SET_ACTION_POINT(Init, 0x0003);

   HXmlTree Cfg;

   {
      HXmlTree CfgCust;
      HXmlTree CfgAuth;
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         BkPrintError("Line %d : Load config.xml fail, %s",
            __LINE__, Cfg.GetErrorString().c_str());
         return false;
      }
      LoadFromCfg(Cfg, true);
#ifndef SIMBAT_ENTERPRISE
      if ( MyRole == nrGateway )
      {
         snprintf(ErrMsg, 2048, "Only supported in Enterprise Edition");
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
#endif
      snprintf(FileName, 500, "%s/etc/config_cust.xml", ShrDir.c_str());
      ConfigCustFile = FileName;
      if ( FileExist(ConfigCustFile.c_str()) )
      {
         if ( !CfgCust.LoadFromFile(ConfigCustFile.c_str()) )
         {
            snprintf(ErrMsg, 2048, "Load config_cust.xml fail, %s", CfgCust.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return false;
         }
         LoadFromCfgCust(CfgCust);
      }
      if ( MyRole == nrMaster || MyRole == nrGateway )
      {
         snprintf(FileName, 500, "%s/etc/auth.xml", ShrDir.c_str());
         if ( !CfgAuth.LoadFromFile(FileName) )
         {
            snprintf(ErrMsg, 2048, "Load auth.xml fail, %s", CfgAuth.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return false;
         }
         LoadFromCfgAuth(CfgAuth);
      }
   }

   SET_ACTION_POINT(Init, 0x0004);
   snprintf(ErrMsg, 2048, "MyId = %d, MyRole = %d", MyId, MyRole);
   Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
   SocketExecNameVec.clear();

   if ( MyRole == nrMaster || MyRole == nrSlave )
   {
      for ( int i = 0; i < NumOfExecutor; i++ )
      {
         snprintf(FileName, 500, "%s/tmp/socket_exec%d", SimbatBase, i);
         SocketExecNameVec.push_back(FileName);
      }
   }

   if ( !LoadClusterInfo(Cfg) )
   {
      return false;
   }

   SET_ACTION_POINT(Init, 0x0005);

   if ( !System.Init() )
   {
      return false;
   }

   SET_ACTION_POINT(Init, 0x0006);
   snprintf(FileName, 500, "%s/status/var_pool", ShrDir.c_str());
   VarpoolFile = FileName;
   snprintf(FileName, 500, "%s/tmp/var_pool.lck", SimbatBase);
   VarpoolLockFile = FileName;
   snprintf(FileName, 500, "%s/status/control.dat", ShrDir.c_str());
   CtldatFile = FileName;
   snprintf(FileName, 500, "%s/tmp/ctldat.lck", SimbatBase);
   CtldatLockFile = FileName;
   LastBatchTime[0] = '\0';
   SET_ACTION_POINT(Init, 0x0007);

   if ( BkFileSize(VarpoolFile.c_str()) == 0 )
   {
      CreateEmptyXmlFile(VarpoolFile, ErrMsg);
   }

   {
      snprintf(FileName, 500, "%s/etc/http_post.ini", SimbatBase);
      if ( BkFileSize(FileName) == 0 )
      {
         CreateEmptyFile(FileName, ErrMsg);
      }
   }

   SET_ACTION_POINT(Init, 0x0008);
   memset(&Ctldat, 0, sizeof(Ctldat));

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      HFileLockRead Lock(CtldatLockFile.c_str());
      FILE *f = fopen(CtldatFile.c_str(), "rb");
      if ( f != NULL )
      {
         fread(&Ctldat, sizeof(Ctldat), 1, f);
         fclose(f);
      }
   }

   SET_ACTION_POINT(Init, 0x0009);

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      Ctldat.ConfigVersion++;
      if ( MyRole == nrMaster )
      {
         Ctldat.VarpoolVersion++;
         HJobSeq::Init();
      }
      WriteCtldat();
      if ( MyRole == nrMaster )
      {
         InitOrResumeBatch();
      }
   }
   else
   {
      snprintf(FileName, 500, "%s/etc/last_batch_time", SimbatBase);
      FILE *f = fopen(FileName, "rb");
      if ( f != NULL )
      {
         fread(LastBatchTime, 8 + 6, 1, f);
         fclose(f);
         LastBatchTime[8 + 6] = '\0';
      }
   }

   SET_ACTION_POINT(Init, 0x000a);

   if ( ClusterFlag && MyRole == nrMaster )
   {
      AnnounceCompleteFlag = false;
      System.RunAnnounce();
   }

   SET_ACTION_POINT(Init, 0x000b);

   if ( ( MyRole == nrMaster || MyRole == nrGateway ) &&
        HotbackEnableFlag && !HotbackPausedFlag )
   {
      InitBackupVec();
      DoHotback();
   }

   SET_ACTION_POINT(Init, 0x000c);
   return true;
}

static void DoIt()
{
   SET_ACTION_POINT(DoIt, 0x0001);

   vector<HThread*> ThreadVec;
   char ErrMsg[2048 + 1];

   {
      sigset_t SigSet;

      sigemptyset(&SigSet);
      sigaddset(&SigSet, SIGTERM);
      sigprocmask(SIG_BLOCK, &SigSet, NULL);
   }

   InputQueue.Init(64);
   StatusInputQueue.Init(StatusInputQueueSize);
   DispatchQueue.Init(DispatchQueueSize);
   PostFuncQueue.Init(1024);

   SET_ACTION_POINT(DoIt, 0x0002);

   HThread *SignalThread = new HSignalThread(SIGTERM);
   SignalThread->Start();
   HThread *CheckerThread = new HCheckerThread();
   CheckerThread->Start();

   SET_ACTION_POINT(DoIt, 0x0003);

   for ( int i = 0; i < 10; i++ )
   {
      HThread *PostFuncThread = new HPostFuncThread();
      PostFuncThread->Start();
      ThreadVec.push_back(PostFuncThread);
   }

   for ( int i = 0; i < NumOfListener; i++ )
   {
      HListenerThread *Thread = new HListenerThread(i);
      Thread->Start();
      ThreadVec.push_back(Thread);
   }

   if ( MyRole == nrMaster )
   {
      for ( int i = 0; i < NumOfDispatcher; i++ )
      {
         HDispatcherThread *Thread = new HDispatcherThread;
         Thread->Start();
         ThreadVec.push_back(Thread);
      }
   }

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      for ( map<HString, HBackupInfo_run>::iterator Iter = BackupMap.begin();
            Iter != BackupMap.end(); Iter++ )
      {
         HBackupThread *Thread = new HBackupThread(Iter->second);
         Thread->Start();
         ThreadVec.push_back(Thread);
      }
   }

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      HAlarmThread *Thread = new HAlarmThread;
      Thread->Start();
      ThreadVec.push_back(Thread);
   }

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      HOutlineThread *Thread = new HOutlineThread;
      Thread->Start();
      ThreadVec.push_back(Thread);
   }

   if ( MyRole == nrMaster )
   {
      int PipeFd[2];
      pipe(PipeFd);
      SockDb = PipeFd[1];
      HSyncStatusThread *Thread = new HSyncStatusThread(PipeFd[0]);
      Thread->Start();
      ThreadVec.push_back(Thread);
   }

   if ( MyRole == nrMaster )
   {
      WriteHisThread = new HWriteHisThread();
      WriteHisThread->Start();
      ThreadVec.push_back(WriteHisThread);
   }

   SET_ACTION_POINT(DoIt, 0x0004);

   time_t LastWakeUpTime = 0;
   time_t LastMonTime = 0;

   gettimeofday(&LastScheduleTime, NULL);

   for ( ; !ExitFlag; BsSleep(100) )
   {
      SET_ACTION_POINT(DoIt, 0x0005);
      if ( ParaMonFile != NULL )
      {
         time_t CurrTime = time(NULL);
         if ( CurrTime - LastMonTime > 2 )
         {
            char TimeStr[200 + 1];
            BkCurrTime(TimeStr);
            LastMonTime = CurrTime;
            int TotalPara = 0;
            int NodeQty = ClusterNodeVec.size();
            for ( int i = 0; i < NodeQty; i++ )
            {
               HClusterNodeInfo &Node = ClusterNodeVec[i];
               int UsedQty = Node.RunningJobNum - Node.RunningWaitJobNum;
               TotalPara += UsedQty;
            }
            fprintf(ParaMonFile, "%s, total, %d\n", TimeStr, TotalPara);
            fflush(ParaMonFile);
         }
      }
      SET_ACTION_POINT(DoIt, 0x0006);
      FlushInput();
      SET_ACTION_POINT(DoIt, 0x0007);
      if ( ExitFlag )
      {
         break;
      }
      if ( MyRole == nrMaster )
      {
         FlushStatus();
      }
      else if ( MyRole == nrSlave )
      {
         SlaveFlushStatus();
      }
      SET_ACTION_POINT(DoIt, 0x0008);
      if ( ExitFlag )
      {
         break;
      }
      if ( MyRole == nrMaster )
      {
         if ( Batch.Status != bsPaused )
         {
            int SyncQty;
            bool r = HEventLogger::CheckSync(SyncQty);
            if ( !r )
            {
               if ( Batch.Status == bsStarted )
               {
                  PauseReason = prFsCheck;
                  Batch.Status = bsPaused;
                  DoBatchTrigger("PAUSE");
                  snprintf(ErrMsg, 2048, "File system '%s/status' full", ShrDir.c_str());
                  PushAlarmObj("FS_FULL", ErrMsg);
               }
            }
         }
      }
      SET_ACTION_POINT(DoIt, 0x0009);
      bool WakeUpFlag = false;
      struct timeval CurrTimeval;
      gettimeofday(&CurrTimeval, NULL);
      time_t CurrTime = CurrTimeval.tv_sec;
      if ( CurrTime - LastWakeUpTime > 10 )
      {
         WakeUpFlag = true;
         LastWakeUpTime = CurrTime;
      }
      if ( ( MyRole == nrMaster || MyRole == nrGateway ) &&
           BkSubTimeval(&CurrTimeval, &LastScheduleTime) >= 300 )
      {
         if ( HotbackEnableFlag && !HotbackPausedFlag )
         {
            if ( !HotbackObjVec.empty() )
            {
               DoHotback();
            }
         }
      }
      SET_ACTION_POINT(DoIt, 0x000a);
      if ( MyRole == nrMaster && BkSubTimeval(&CurrTimeval, &LastScheduleTime) >= 300 )
      {
         GraphMutex.Lock();
         Schedule();
         GraphMutex.Unlock();
      }
      SET_ACTION_POINT(DoIt, 0x000b);
      if ( MyRole == nrSlave && CurrTime - SlaveSignonTime >= 5 )
      {
         for ( ; ; )
         {
            bool NeedRetry = false;
            SlaveSignon(NeedRetry);
            if ( !NeedRetry )
            {
               break;
            }
         }
         SlaveSignonTime = CurrTime;
         continue;
      }
      SET_ACTION_POINT(DoIt, 0x000c);
      if ( !WakeUpFlag )
      {
         continue;
      }
      if ( MyRole == nrMaster && DownloadAuth && !GatewayAddrVec.empty() && CurrTime - TouchGatewayTime >= 60 )
      {
         TouchGateway();
         TouchGatewayTime = CurrTime;
      }
      if ( MyRole == nrMaster &&
           ( Batch.Status == bsNotStarted || Batch.Status == bsStopped ) &&
           CurrTime - ServerStartTime > 48 * 60 * 60 &&
           CurrTime - LastActionTime > 10 * 60 &&
           IsSafeTime(CurrTime, NextDailyBatchTime) &&
           IsSafeTime(CurrTime, AlarmBatchStartTime) &&
           IsSafeTime(CurrTime, NextBatchAlarmTime) &&
           IsSafeTime(CurrTime, NextBatchEndAlarmTime)
         )
      {
         snprintf(ErrMsg, 2048, "Scheduler shutdown normally");
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         BkPrintError(ErrMsg);
         ExitFlag = true;
         break;
      }
      SET_ACTION_POINT(DoIt, 0x000d);
      if ( MyRole == nrMaster && CurrTime - MasterCheckTime >= 10 )
      {
         MasterCheckTime = CurrTime;
         bool PostFlag = true;
         for ( int i = 1; i < ClusterNodeVec.size(); i++ )
         {
            HClusterNodeInfo &Node = ClusterNodeVec[i];
            if ( CurrTime - Node.LastTouchTime > SlaveSignonCheckTime )
            {
               Node.SignonFlag = false;
            }
         }
         for ( map<HString, job_info>::iterator Iter = Graph.begin();
               Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( !Job.PartFlag )
            {
               if ( PostFlag && Job.Status == jsUnknown &&
                    Job.ServerId != MyId && !Job.SlaveNotSignon )
               {
                  HPostFuncInfo PostFuncInfo;
                  PostFuncInfo.Func = fcInquiryCommand;
                  PostFuncInfo.FullName = Job.Name;
                  PostFuncInfo.ServerId = Job.ServerId;
                  PostFuncInfo.JobStartTime = Job.StartTime;
                  if ( !PostFuncQueue.MasterPushBackNoWait(PostFuncInfo) )
                  {
                     PostFlag = false;
                  }
               }
               if ( Job.ServerId != MyId && Job.Status == jsRunning )
               {
                  HClusterNodeInfo &Node = ClusterNodeVec[Job.ServerId];
                  if ( CurrTime - Node.LastTouchTime > SlaveSignonCheckTime )
                  {
                     snprintf(ErrMsg, 2048, "Server = %d, Job = %s, slave down?",
                        Job.ServerId, Job.Name.c_str());
                     Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                     ChangeStatus(Job.Name, jsUnknown, 0, 0, 0);
                     Job.SlaveNotSignon = true;
                  }
               }
               continue;
            }
            if ( Job.Status == jsWaiting || Job.Status == jsSuccess )
            {
               continue;
            }
            for ( int i = 0; i < Job.PartVec.size(); i++ )
            {
               part_info &Part = Job.PartVec[i];
               if ( PostFlag && Part.Status == jsUnknown &&
                    Part.ServerId != MyId && !Part.SlaveNotSignon )
               {
                  HPostFuncInfo PostFuncInfo;
                  PostFuncInfo.Func = fcInquiryCommand;
                  PostFuncInfo.FullName = Job.Name + JobNameDelimiter + PartIdxString(i);
                  PostFuncInfo.ServerId = Part.ServerId;
                  PostFuncInfo.JobStartTime = Part.StartTime;
                  if ( !PostFuncQueue.MasterPushBackNoWait(PostFuncInfo) )
                  {
                     PostFlag = false;
                  }
               }
               if ( Part.ServerId != MyId && Part.Status == jsRunning )
               {
                  HClusterNodeInfo &Node = ClusterNodeVec[Part.ServerId];
                  if ( CurrTime - Node.LastTouchTime > SlaveSignonCheckTime )
                  {
                     HString FullName = Job.Name + JobNameDelimiter + PartIdxString(i);
                     snprintf(ErrMsg, 2048, "Server = %d, Job = %s, slave down?",
                        Part.ServerId, FullName.c_str());
                     Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
                     ChangeStatus(FullName, jsUnknown, 0, 0, 0);
                     Part.SlaveNotSignon = true;
                  }
               }
            }
         }
      }
      SET_ACTION_POINT(DoIt, 0x000e);
      if ( MyRole == nrMaster && Batch.Status == bsStarted && CurrTime - FsCheckTime >= 60 )
      {
         FsCheckTime = CurrTime;
         bool Found = false;
         for ( int i = 0; i < FsCheckVec.size(); i++ )
         {
            BK_FS_INFO FsInfo;
            if ( BkGetFsInfo(FsCheckVec[i].Path.c_str(), &FsInfo) == 0 )
            {
               if ( FsInfo.free_blocks < FsCheckVec[i].Size )
               {
                  snprintf(ErrMsg, 2048, "File system %s is almost full, free size(M) = %d < %d",
                     FsCheckVec[i].Path.c_str(), FsInfo.free_blocks, FsCheckVec[i].Size );
                  Syslog.Log(__FILE__, __LINE__, llError, "OTHER", ErrMsg);
                  Found = true;
                  PushAlarmObj("FS_FULL", ErrMsg);
                  break;
               }
            }
         }
         if ( Found )
         {
            HPostFuncInfo PostFuncInfo;
            PostFuncInfo.Func = fcPauseBatch;
            PostFuncQueue.MasterPushBackNoWait(PostFuncInfo);
         }
      }
      SET_ACTION_POINT(DoIt, 0x000f);
      if ( MyRole == nrMaster && AlarmBatchName != "" &&
           NextBatchAlarmTime > 0 && CurrTime >= NextBatchAlarmTime )
      {
         if ( AlarmBatchStartTime == 0 ||
              NextBatchAlarmTime - AlarmBatchStartTime > 5 * 60 * 60 )
         {
            snprintf(ErrMsg, 2048, "Batch alarm %s", BatchAlarm.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
            DoBatchAlarmTrigger("ALARM_START");
            PushAlarmObj("BATCH_NOT_START", ErrMsg);
            BatchAlarmedFlag = true;
         }
         NextBatchAlarmTime += 24 * 60 * 60;
         snprintf(ErrMsg, 2048, "NextBatchAlarmTime = %s", TimeToString(NextBatchAlarmTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      }
      SET_ACTION_POINT(DoIt, 0x0010);
      if ( MyRole == nrMaster && BatchAlarmedFlag && CurrTime + 24 * 60 * 60 - NextBatchAlarmTime > 5 * 60 * 60 )
      {
         snprintf(ErrMsg, 2048, "Batch alarm %s closed", BatchAlarm.c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         BatchAlarmedFlag = false;
      }
      if ( MyRole == nrMaster && AlarmBatchName != "" &&
           NextBatchEndAlarmTime > 0 && CurrTime >= NextBatchEndAlarmTime )
      {
         if ( AlarmBatchName == Batch.Name &&
              Batch.Status != bsNotStarted && Batch.Status != bsStopped )
         {
            snprintf(ErrMsg, 2048, "Batch end alarm %s", BatchAlarm.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
            DoBatchAlarmTrigger("ALARM_END");
            PushAlarmObj("BATCH_NOT_END", ErrMsg);
            BatchEndAlarmedFlag = true;
         }
         NextBatchEndAlarmTime += 24 * 60 * 60;
         snprintf(ErrMsg, 2048, "NextBatchEndAlarmTime = %s", TimeToString(NextBatchEndAlarmTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      }
      SET_ACTION_POINT(DoIt, 0x0011);
      if ( MyRole == nrMaster && BatchEndAlarmedFlag && CurrTime + 24 * 60 * 60 - NextBatchEndAlarmTime > 5 * 60 * 60 )
      {
         snprintf(ErrMsg, 2048, "Batch end alarm %s closed", BatchAlarm.c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
         BatchEndAlarmedFlag = false;
      }
      if ( MyRole == nrMaster && DailyBatchName != "" && CurrTime >= NextDailyBatchTime )
      {
         if ( Batch.Status == bsNotStarted || Batch.Status == bsStopped )
         {
            snprintf(ErrMsg, 2048, "Starting daily batch %s", DailyBatchName.c_str());
            Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            HString ErrStr;
            StartBatch(DailyBatchName.c_str(), ErrStr);
         }
         else
         {
            snprintf(ErrMsg, 2048, "Daily batch %s can not be started", DailyBatchName.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
         }
         SortTimeVec(DailyBatchTimeVec);
         NextDailyBatchTime = DailyBatchTimeVec[0].Time;
         snprintf(ErrMsg, 2048, "NextDailyBatchTime = %s", TimeToString(NextDailyBatchTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
      }
      SET_ACTION_POINT(DoIt, 0x0012);
      if ( MyRole == nrMaster && Batch.Status != bsNotStarted && Batch.Status != bsStopped )
      {
         for ( map<HString, time_t>::iterator AlarmIter = JobAlarmMap.begin();
               AlarmIter != JobAlarmMap.end(); )
         {
            map<HString, time_t>::iterator Iter2 = AlarmIter;
            AlarmIter++;
            HString JobName = Iter2->first;
            time_t AlarmTime = Iter2->second;
            if ( CurrTime >= AlarmTime )
            {
               snprintf(ErrMsg, 2048, "Job alarm %s %s", JobName.c_str(), Graph[JobName].AlarmTime.c_str());
               Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
               job_info &Job = Graph[JobName];
               Job.AlarmedFlag = true;
               if ( Job.AlarmTimeType == attDurationFromTheJobStarting )
               {
                  DoTrigger(JobName, "ALARM_END");
                  PushAlarmObj("JOB_NOT_END", ErrMsg, Job);
               }
               else
               {
                  DoTrigger(JobName, "ALARM_START");
                  PushAlarmObj("JOB_NOT_START", ErrMsg, Job);
               }
               JobAlarmMap.erase(Iter2);
            }
         }
      }
      SET_ACTION_POINT(DoIt, 0x0013);
   }

   int SyncQty;

   SET_ACTION_POINT(DoIt, 0x0014);
   HEventLogger::Sync(SyncQty);
   SET_ACTION_POINT(DoIt, 0x0015);

// 杀掉所有event_wait作业
   if ( MyRole == nrMaster )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
           Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( ( Job.Type != jtEventWait && Job.Type != jtTime ) || Job.Status != jsRunning )
         {
            continue;
         }
         kill(-Job.Pid, SIGINT);
      }
   }

   SET_ACTION_POINT(DoIt, 0x0016);

// Cleanup

   for ( int i = 0; i < ThreadVec.size(); i++ )
   {
      HThread *Thread = ThreadVec[i];
      Thread->Wait();
      delete Thread;
   }

   SET_ACTION_POINT(DoIt, 0x0017);
   delete CheckerThread;
   delete SignalThread;
   InputQueue.Close();
   StatusInputQueue.Close();
   PostFuncQueue.Close();
   DispatchQueue.Close();
   SET_ACTION_POINT(DoIt, 0x0018);
}

static bool DoBackupInit(const HString &Addr, int Port, const char *BatchTime, int &JobEventQty)
{
   char AddrStr[200 + 1];
   char Buf[500 + 1];
   char ErrMsg[2048 + 1];

   snprintf(ErrMsg, 2048, "DoBackupInit begin, Addr = %s:%d, BatchTime = %s", Addr.c_str(), Port, BatchTime);
   Syslog.Log(__FILE__, __LINE__, llInfo, "ENTRY", ErrMsg);
   snprintf(Buf, 500, "l|||%s", BatchTime);
   snprintf(AddrStr, 200, "%s:%d", Addr.c_str(), Port);

   HString Result;
   bool SuccFlag = CallServer(Syslog, 20000, AddrStr, "BACKUP_INIT", "", Buf, false, Result, false, NULL, NULL);

   snprintf(ErrMsg, 2048, "DoHotbackInit end, SuccFlag = %d, Result = %s", SuccFlag, Result.c_str());
   Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);

   if ( SuccFlag )
   {
      JobEventQty = StrToInt(Result.c_str());
   }

   return SuccFlag;
}

static bool DoBackupBatchStatus(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boBatchStatus, "", 0, Len);
}

static bool DoBackupJobEvents(const HString &Addr, int Port, int &JobEventQty)
{
   int Total = HEventLogger::GetFileUsed();

   if ( JobEventQty >= Total )
   {
      return true;
   }

   if ( SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boJobEvents, "",
           sizeof(HJobEvent) * JobEventQty, sizeof(HJobEvent) * ( Total - JobEventQty )) )
   {
      JobEventQty = Total;
      return true;
   }
   else
   {
      return false;
   }
}

static bool DoBackupCtldat(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/control.dat", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boCtldat, "", 0, Len);
}

static bool DoBackupVarpool(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/var_pool", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boVarpool, "", 0, Len);
}

static bool DoBackupConfig(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/config_cust.xml", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boConfig, "", 0, Len);
}

static bool DoBackupCurrentSbt(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boCurrentSbt, "", 0, Len);
}

static bool DoBackupEvent(const HString &Addr, int Port, const char *ObjName)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/events/%s", ShrDir.c_str(), ObjName);

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boEvent, ObjName, 0, Len);
}

static bool DoBackupAuth(const HString &Addr, int Port)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/auth.xml", ShrDir.c_str());

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boAuth, "", 0, Len);
}

static bool DoBackupShr(const HString &Addr, int Port, const char *ObjName)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/%s", ShrDir.c_str(), ObjName);

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boShr, ObjName, 0, Len);
}

static bool DoBackupJobLog(const HString &Addr, int Port, const char *ObjName, unsigned long Offset)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/%s", LogDir.c_str(), ObjName);

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boJobLog, ObjName, Offset, Len);
}

static bool DoBackupOther(const HString &Addr, int Port, const char *ObjName)
{
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/%s", SimbatBase, ObjName);

   long Len = FileExist(FileName) ? BkFileSize(FileName) : -1;

   return SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Addr.c_str(), Port, boOther, ObjName, 0, Len);
}

static void PushBackupObj(const HBackupObj &Obj)
{
   if ( Obj.Type == boInit )
   {
      HotbackObjVec.clear();
      LastHotbackSuccTime = 0;
   }

   HotbackObjVec.push_back(Obj);

   for ( map<HString, HBackupInfo_run>::iterator Iter = BackupMap.begin();
         Iter != BackupMap.end(); Iter++ )
   {
      HBackupInfo_run &Backup = Iter->second;
      Backup.Mutex.Lock();
      if ( Obj.Type == boInit )
      {
         Backup.BackupObjVec.clear();
         Backup.Idx = 0;
      }
      Backup.BackupObjVec.push_back(Obj);
      Backup.Mutex.Unlock();
   }
}

static void PushAlarmObj(const HAlarmObj &Obj)
{
   AlarmMutex.Lock();
   AlarmVec.push_back(Obj);
   AlarmMutex.Unlock();
}

static void PushAlarmObj(const HString &Type, const HString &Msg)
{
   HAlarmObj Obj;
   char TmpStr[200 + 1];

   Obj.System = AlarmSystemName;
   BkCurrTime(TmpStr);
   Obj.Time = TmpStr;
   HJobSeq::GetJobSeq(TmpStr);
   Obj.Seq = TmpStr;
   Obj.BatchDate = BatchDate;
   Obj.Type = Type;
   Obj.Msg = Msg;
   PushAlarmObj(Obj);
}

static void PushAlarmObj(const HString &Type, const HString &Msg, const job_info &Job)
{
   HAlarmObj Obj;
   char TmpStr[200 + 1];

   Obj.System = AlarmSystemName;
   BkCurrTime(TmpStr);
   Obj.Time = TmpStr;
   HJobSeq::GetJobSeq(TmpStr);
   Obj.Seq = TmpStr;
   Obj.BatchDate = BatchDate;
   Obj.Type = Type;
   Obj.Msg = Msg;

   {
      vector<HString> StrVec;
      Job.Xml->GetValues("DESCRIPTION", StrVec);
      Obj.Remark = JoinStrings(StrVec, '\n');
   }

   Obj.Tag = Job.Tag;

   {
      HString &Str = Obj.Remark;
      for ( int i = 0; i < Str.length(); i++ )
      {
         if ( Str[i] == '|' || Str[i] == '\n' )
         {
            Str[i] == ' ';
         }
      }
   }

   {
      HString &Str = Obj.Tag;
      for ( int i = 0; i < Str.length(); i++ )
      {
         if ( Str[i] == '|' || Str[i] == '\n' )
         {
            Str[i] == ' ';
         }
      }
   }

   PushAlarmObj(Obj);
}

static void InitBackupVec()
{
   PushBackupObj(HBackupObj(boInit, LastBatchTime));
   PushBackupObj(boBatchStatus);
   PushBackupObj(boJobEvents);
   PushBackupObj(boCtldat);
   PushBackupObj(boVarpool);
   PushBackupObj(boConfig);
   PushBackupObj(boCurrentSbt);
   PushBackupObj(boAuth);

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/events", ShrDir.c_str());

   DIR *Dir;
   Dir = opendir(FileName);

   if ( Dir == NULL )
   {
      return;
   }

   for ( ; ; )
   {
      struct dirent *Ent;
      Ent = readdir(Dir);
      if ( Ent == NULL )
      {
         break;
      }
      snprintf(FileName, 500, "%s/events/%s", ShrDir.c_str(), Ent->d_name);
      if ( BkStrRCmp(FileName, ".file") == 0 )
      {
         PushBackupObj(HBackupObj(boEvent, Ent->d_name));
      }
   }

   closedir(Dir);
}

static void DoHotback()
{
   char ErrMsg[2048 + 1];

   if ( !AnnounceCompleteFlag || !HotbackEnableFlag || HotbackPausedFlag )
   {
      return;
   }

   time_t CurrTime = time(NULL);

   if ( CurrTime - LastHotbackFailTime < 60 ) // 热备失败则暂停一分钟
   {
      return;
   }

   for ( int i = 0; i < HotbackObjVec.size(); i++ )
   {
      HBackupObj &Obj = HotbackObjVec[i];
      if ( Obj.Type == boNull ) // 已经处理过了的
      {
         continue;
      }
      bool SuccFlag = false;
      switch ( Obj.Type )
      {
      case boInit :
         SuccFlag = DoBackupInit(HotbackAddr, HotbackPort, Obj.Name, HotbackJobEventQty);
         break;
      case boBatchStatus :
         SuccFlag = DoBackupBatchStatus(HotbackAddr, HotbackPort);
         break;
      case boJobEvents :
         SuccFlag = DoBackupJobEvents(HotbackAddr, HotbackPort, HotbackJobEventQty);
         break;
      case boCtldat :
         SuccFlag = DoBackupCtldat(HotbackAddr, HotbackPort);
         break;
      case boVarpool :
         SuccFlag = DoBackupVarpool(HotbackAddr, HotbackPort);
         break;
      case boConfig :
         SuccFlag = DoBackupConfig(HotbackAddr, HotbackPort);
         break;
      case boCurrentSbt :
         SuccFlag = DoBackupCurrentSbt(HotbackAddr, HotbackPort);
         break;
      case boEvent :
         SuccFlag = DoBackupEvent(HotbackAddr, HotbackPort, Obj.Name);
         break;
      case boAuth :
         SuccFlag = DoBackupAuth(HotbackAddr, HotbackPort);
         break;
      case boShr :
         SuccFlag = DoBackupShr(HotbackAddr, HotbackPort, Obj.Name);
         break;
      case boJobLog :
         SuccFlag = DoBackupJobLog(HotbackAddr, HotbackPort, Obj.Name, Obj.Offset);
         break;
      default :
         SuccFlag = DoBackupOther(HotbackAddr, HotbackPort, Obj.Name);
      }
      CurrTime = time(NULL);
      if ( SuccFlag )
      {
         Obj.Type = boNull;
         LastHotbackSuccTime = CurrTime;
         continue;
      }
      LastHotbackFailTime = CurrTime;
      if ( LastHotbackSuccTime > 0 && CurrTime - LastHotbackSuccTime > 1200 ) // 超过20分钟热备一直失败则PAUSE热备
      {
         snprintf(ErrMsg, 2048, "LastHotbackSuccTime = %s, hotback paused", TimeToString(LastHotbackSuccTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         HotbackPausedFlag = true;
         {
            HXmlTree Cfg;
            HFileLockWrite Lock(ConfigLockFile.c_str());
            if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
            {
               snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               Cfg.SetValue("HOTBACK_PAUSED", "true");
               Cfg.SaveToFile(ConfigFile.c_str());
            }
         }
      }
      else if ( Batch.Status == bsStarted )
      {
         PauseReason = prHotback;
         Batch.Status = bsPaused;
         DoBatchTrigger("PAUSE");
         PushAlarmObj("HOTBACK_FAIL", "hotback fail");
      }
      return;
   }

   HotbackObjVec.clear();
}

static void FuncAnnounceComplete(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 4 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   ReturnSuccess(Input);

   if ( Input.ArgVec[3] == "N" )
   {
      snprintf(ErrMsg, 2048, "Announce fail");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      exit(8);
   }

   char FileName[500 + 1];

   AnnounceCompleteFlag = true;
}

static void FuncCalcCriticalPath(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status == bsNotStarted )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   char FileName1[500 + 1];
   snprintf(FileName1, 500, "inquiry_result1_%d_%d", getpid(), Input.ThreadIdx);
   FILE *f = fopen(FileName1, "wt");

   if ( f == NULL )
   {
      goto end;
   }

   GraphMutex.Lock();
   HCritical::WriteCriticalPath(f);
   GraphMutex.Unlock();
   fclose(f);
   CompressFileToSocket(Input.Fd, FileName1,
      CodeSet, StrToCodeSet(Input.CodeSet), false, Input.ThreadIdx);

end :
   unlink(FileName1);
   close(Input.Fd);
}

static void FuncChangeDate(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 6 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString Date = Input.ArgVec[3];
   HString NextDate = Input.ArgVec[4];
   HString EndDate = Input.ArgVec[5];

   RightTrim(Date);
   RightTrim(NextDate);
   RightTrim(EndDate);

   if ( NextDate != "" && !CheckDate(NextDate.c_str()) )
   {
      snprintf(ErrMsg, 2048, "Invalid date '%s'", NextDate.c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( EndDate != "" && !CheckDate(EndDate.c_str()) )
   {
      snprintf(ErrMsg, 2048, "Invalid date '%s'", EndDate.c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   bool ChangedFlag = false;

   if ( NextScheduleDate != NextDate )
   {
      NextScheduleDate = NextDate;
      SetEnv("SIMBAT_NEXT_SCHEDULE_DATE", NextScheduleDate.c_str(), false);
      ChangedFlag = true;
   }

   if ( EndBatchDate != EndDate )
   {
      EndBatchDate = EndDate;
      SetEnv("SIMBAT_END_DATE", EndBatchDate.c_str(), false);
      ChangedFlag = true;
   }

   ReturnSuccess(Input);

   if ( ChangedFlag )
   {
      PushBackupObj(boVarpool);
      AddVarpoolVersion();
      BsWriteToSocket(SockDb, "V", 1, 0);
   }
}

static void FuncChangeJobCondition(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   char TmpStr[500 + 1];

   if ( Input.ArgVec.size() < 9 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString FullName = Input.ObjName;
   bool CondNot = Input.ArgVec[4] == "Y";
   HString ConditionFile = Input.ArgVec[5];
   HString SpecialDate = Input.ArgVec[6];
   HString LoopStartTime = Input.ArgVec[7];
   HString LoopEndTime = Input.ArgVec[8];

   if ( Graph.find(FullName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[FullName];

   if ( Job.Type == jtExternJob )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   Job.CondNot = CondNot;
   Job.ConditionFile = ConditionFile;
   Job.SpecialDate = SpecialDate;

   if ( LoopStartTime != Job.LoopStartTimeStr )
   {
      Job.LoopStartTimeStr = LoopStartTime;
      if ( Job.LoopStartTimeStr != "" )
      {
         int Delta = 0;
         const char *s = Job.LoopStartTimeStr.c_str();
         char w[80 + 1];
         int Pos = 0;
         int Hour;
         int Min;
         Pos += ReadWord(s + Pos, w, " :", 80);
         Hour = StrToInt(w);
         Pos += ReadWord(s + Pos, w, " :", 80);
         if ( strcmp(w, ":") != 0 )
         {
            snprintf(ErrMsg, 2048, "Job %s, loop start time '%s' syntax error", Job.Name.c_str(), Job.LoopStartTimeStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(Input, ErrMsg);
            return;
         }
         Pos += ReadWord(s + Pos, w, " :", 80);
         Min = StrToInt(w);
         snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
         if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
         {
            Delta = 24 * 60 * 60;
         }
         Job.LoopStartTime = StringToTime(TmpStr) + Delta;
      }
      else
      {
         Job.LoopStartTime = 0;
      }
      Job.Xml->SetProperty("LOOP_START_TIME", LoopStartTime);
   }

   if ( LoopEndTime != Job.LoopEndTimeStr )
   {
      Job.LoopEndTimeStr = LoopEndTime;
      if ( Job.LoopEndTimeStr != "" )
      {
         int Delta = 0;
         const char *s = Job.LoopEndTimeStr.c_str();
         char w[80 + 1];
         int Pos = 0;
         int Hour;
         int Min;
         Pos += ReadWord(s + Pos, w, " :", 80);
         Hour = StrToInt(w);
         Pos += ReadWord(s + Pos, w, " :", 80);
         if ( strcmp(w, ":") != 0 )
         {
            snprintf(ErrMsg, 2048, "Job %s, loop end time '%s' syntax error", Job.Name.c_str(), Job.LoopEndTimeStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(Input, ErrMsg);
            return;
         }
         Pos += ReadWord(s + Pos, w, " :", 80);
         Min = StrToInt(w);
         snprintf(TmpStr, 500, "%.8s%02d%02d00", Batch.StartTime, Hour, Min);
         if ( strcmp(TmpStr, Batch.StartTime) < 0 && AcrossDayEnd )
         {
            Delta = 24 * 60 * 60;
         }
         Job.LoopEndTime = StringToTime(TmpStr) + Delta;
      }
      else
      {
         Job.LoopEndTime = 0;
      }
      Job.Xml->SetProperty("LOOP_END_TIME", LoopEndTime);
   }

   Job.Xml->SetProperty("COND_NOT", CondNot ? "Y" : "N");
   Job.Xml->SetProperty("CONDITION_FILE", ConditionFile);
   Job.Xml->SetProperty("SPECIAL_DATE", SpecialDate);

   if ( Job.Type == jtGroup )
   {
      group_info &Group = GroupMap[Job.Group];
      Group.CondNot = CondNot;
      Group.ConditionFile = ConditionFile;
      Group.SpecialDate = SpecialDate;
      if ( SpecialDate == "" )
      {
         Group.ConditionDateCache = 1;
      }
      else
      {
         Group.ConditionDateCache = 2;
      }
      if ( ConditionFile == "" )
      {
         Group.ConditionFileCache = 1;
      }
      else
      {
         Group.ConditionFileCache = 2;
      }
   }

   ChangeJobConditionFlag = true;
   ReturnSuccess(Input);

   {
      vector<HString> StrVec;
      SplitString(FullName, JobNameDelimiter[0], StrVec);
      HString GroupName = StrVec[0];
      HString JobName = StrVec[1];
      HXmlTree Xml;
      char FileName[500 + 1];
      snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());
      if ( !Xml.LoadFromFile(FileName) )
      {
         snprintf(ErrMsg, 2048, "Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      vector<HXmlTree*> Groups;
      Xml.GetChildren("GROUP", Groups);
      for ( int i = 0; i < Groups.size(); i++ )
      {
         if ( Groups[i]->GetPropertyUpper("NAME") != GroupName )
         {
            continue;
         }
         vector<HXmlTree*> Jobs;
         Groups[i]->GetChildren("JOB", Jobs);
         for ( int j = 0; j < Jobs.size(); j++ )
         {
            HXmlTree *JobXml = Jobs[j];
            if ( JobXml->GetPropertyUpper("NAME") != JobName )
            {
               continue;
            }
            JobXml->SetProperty("COND_NOT", CondNot ? "Y" : "N");
            JobXml->SetProperty("CONDITION_FILE", ConditionFile);
            JobXml->SetProperty("SPECIAL_DATE", SpecialDate);
            JobXml->SetProperty("LOOP_START_TIME", LoopStartTime);
            JobXml->SetProperty("LOOP_END_TIME", LoopEndTime);
            break;
         }
      }
      Xml.SaveToFile(FileName);
      PushBackupObj(boCurrentSbt);
   }
}

static void FuncConfigure(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 3 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   {
      vector<HString> TmpVec(Input.ArgVec.begin() + 3, Input.ArgVec.end());
      snprintf(ErrMsg, 2048, "Input is %s", JoinStrings(TmpVec, '|').c_str());
      Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
   }

   HXmlTree CfgCust;
   char TmpStr[500 + 1];

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( FileExist(ConfigCustFile.c_str()) )
      {
         if ( !CfgCust.LoadFromFile(ConfigCustFile.c_str()) )
         {
            snprintf(ErrMsg, 2048, "Load config_cust.xml fail, %s", CfgCust.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(Input, ErrMsg);
            return;
         }
      }
   }

   if ( Input.ArgVec.size() > 4 )
   {
      int NewValue = StrToInt(Input.ArgVec[4].c_str());
      if ( NewValue <= 0 )
      {
         NewValue = 5;
      }
      if ( NewValue != ParaJobNum )
      {
         snprintf(TmpStr, 500, "%d", NewValue);
         CfgCust.SetValue("PARA_JOB_NUM", TmpStr);
      }
   }

   if ( Input.ArgVec.size() > 5 )
   {
      HCodeSet NewValue = StrToCodeSet(Input.ArgVec[5]);
      if ( NewValue != CodeSet )
      {
         CfgCust.SetValue("CODE_SET", CodeSetToStr(NewValue));
      }
   }

   if ( Input.ArgVec.size() > 6 )
   {
      HString NewValue = Input.ArgVec[6];
      if ( NewValue != DailyBatch )
      {
         HString BatchName;
         vector<HHourMin> TimeVec;
         if ( !ParseDailyBatchString(NewValue, BatchName, TimeVec) )
         {
            snprintf(ErrMsg, 2048, "Daily batch(%s) syntax error", NewValue.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            ReturnFail(Input, ErrMsg);
            return;
         }
         CfgCust.SetValue("DAILY_BATCH", NewValue);
      }
   }

   if ( Input.ArgVec.size() > 7 )
   {
      HString NewValue = Input.ArgVec[7];
      if ( NewValue != BatchAlarm )
      {
         HString BatchName;
         int Hour;
         int Min;
         int EndHour;
         int EndMin;
         if ( !ParseBatchAlarmString(NewValue, BatchName, Hour, Min, EndHour, EndMin) )
         {
            snprintf(ErrMsg, 2048, "Batch alarm(%s) syntax error", NewValue.c_str());
            Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
            ReturnFail(Input, ErrMsg);
            return;
         }
         CfgCust.SetValue("BATCH_ALARM", NewValue);
      }
   }

   if ( Input.ArgVec.size() > 8 )
   {
      bool NewValue = ( Input.ArgVec[8] == "true" );
      if ( NewValue != DedicatedFlag )
      {
         CfgCust.SetValue("DEDICATED", NewValue ? "true" : "false");
      }
   }

   {
      HFileLockWrite Lock(ConfigLockFile.c_str());
      CfgCust.SaveToFile(ConfigCustFile.c_str());
   }

   LoadFromCfgCust(CfgCust);
   ReturnSuccess(Input);
   PushBackupObj(boConfig);
   AddConfigVersion();

   if ( ClusterFlag )
   {
      for ( int i = 0; i < ClusterNodeVec.size(); i++ )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[i];
         if ( i == MyId )
         {
            Node.ParaJobNum = ParaJobNum / 2;
            if ( Node.ParaJobNum == 0 )
            {
               Node.ParaJobNum = 1;
            }
         }
         else
         {
            if ( Node.ParaJobNum > 0 )
            {
               Node.ParaJobNum = ParaJobNum;
            }
         }
      }
   }
   else
   {
      HClusterNodeInfo &Node = ClusterNodeVec[0];
      Node.ParaJobNum = ParaJobNum;
   }
}

static void FuncDatabase(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 5 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( !CheckWhiteList(Input, 4) )
   {
      return;
   }

   char InputStr[4096 + 1];
   int InputLen = 0;

   for ( int i = 3; i < Input.ArgVec.size(); i++ )
   {
      if ( i > 3 )
      {
         InputStr[InputLen++] = '|';
      }
      strcpy(InputStr + InputLen, Input.ArgVec[i].c_str());
      ConvString(StrToCodeSet(Input.CodeSet), CodeSet, InputStr + InputLen);
      InputLen += strlen(InputStr + InputLen);
   }

   InputStr[InputLen] = '\0';

   char FileName[500 + 1];
   HString ResultStr;

   snprintf(FileName, sizeof(FileName), "UD:%s/tmp/socket_db", SimbatBase);

   if ( CallServer(Syslog, COMM_TIME_OUT, FileName, "DATABASE", "", InputStr, false, ResultStr) )
   {
      ReturnSuccess(Input, ResultStr.c_str());
   }
   else
   {
      ReturnFail(Input, ResultStr.c_str());
   }
}

static void FuncDownload(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 5 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Input.Fd);
      return;
   }

   HString FileName;
   bool ConvFlag = false;
   HString LockFile;

   if ( Input.ArgVec[3] == "" || Input.ArgVec[3] == "current.sbt" )
   {
      FileName = ShrDir + "/status/current.sbt";
      ConvFlag = true;
   }
   else if ( Input.ArgVec[3] == "para_job_num.csv" )
   {
      FileName = ShrDir + "/etc/para_job_num.csv";
   }
   else if ( Input.ArgVec[3] == "config.xml" )
   {
      LockFile = ConfigLockFile;
      FileName = HString(SimbatBase) + "/etc/config.xml";
   }
   else if ( Input.ArgVec[3] == "http_post.ini" )
   {
      FileName = HString(SimbatBase) + "/etc/http_post.ini";
   }
   else if ( Input.ArgVec[3] == "var_pool" )
   {
      LockFile = VarpoolLockFile;
      FileName = ShrDir + "/status/var_pool";
   }
   else if ( Input.ArgVec[3] == "auth.xml" )
   {
      LockFile = ConfigLockFile;
      FileName = ShrDir + "/etc/auth.xml";
   }
   else if ( BkStrRCmp(Input.ArgVec[3].c_str(), ".sbt") == 0 )
   {
      FileName = HString(SimbatBase) + "/bats/" + Input.ArgVec[3];
      ConvFlag = true;
   }
   else
   {
      FileName = HString(SimbatBase) + "/" + Input.ArgVec[3];
   }

   if ( LockFile == "" )
   {
      if ( Input.ArgVec[4] == "N" )
      {
         WriteFileToSocket(Input.Fd, FileName.c_str());
      }
      else
      {
         if ( ConvFlag )
         {
            CompressFileToSocket(Input.Fd, FileName.c_str(),
               CodeSet, StrToCodeSet(Input.CodeSet), false, Input.ThreadIdx);
         }
         else
         {
            CompressFileToSocket(Input.Fd, FileName.c_str(),
               csNone, csNone, false, Input.ThreadIdx);
         }
      }
   }
   else
   {
      HFileLockRead Lock(LockFile.c_str());
      if ( Input.ArgVec[4] == "N" )
      {
         WriteFileToSocket(Input.Fd, FileName.c_str());
      }
      else
      {
         if ( ConvFlag )
         {
            CompressFileToSocket(Input.Fd, FileName.c_str(),
               CodeSet, StrToCodeSet(Input.CodeSet), false, Input.ThreadIdx);
         }
         else
         {
            CompressFileToSocket(Input.Fd, FileName.c_str(),
               csNone, csNone, false, Input.ThreadIdx);
         }
      }
   }

   close(Input.Fd);
}

static void FuncDump(const HInputInfo &Input)
{
   ReturnSuccess(Input);
   DoDump();
}

static void DoDump()
{
   char TmpStr[500 + 1];
   char FileName[500 + 1];
   char ErrMsg[2048 + 1];

   BkCurrTime(TmpStr);
   snprintf(FileName, 500, "%s/tmp/dump_%s.txt", SimbatBase, TmpStr);

   FILE *f = fopen(FileName, "wt");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Open '%s' fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   fprintf(f, "Version : %s %s\n", VERSION_STRING, EDITION_STRING);
   fprintf(f, "Build time : %s, %s\n", __DATE__, __TIME__);
   fprintf(f, "DumpTime : %s\n\n", TimeToString(time(NULL)).c_str());
   fprintf(f, "SockSch = %d\n", SockSch);
   fprintf(f, "SimbatBase = %s\n", SimbatBase);
   fprintf(f, "ParentPid = %d\n", ParentPid);
   fprintf(f, "ExitFlag = %d\n", ExitFlag);
   fprintf(f, "ServerStartTime = %s\n", TimeToString(ServerStartTime).c_str());
   fprintf(f, "LastActionTime = %s\n", TimeToString(LastActionTime).c_str());
   fprintf(f, "LastScheduleTime = %s.%03d\n", TimeToString(LastScheduleTime.tv_sec).c_str(), LastScheduleTime.tv_usec / 1000);
   fprintf(f, "ConfigFile = %s\n", ConfigFile.c_str());
   fprintf(f, "ConfigCustFile = %s\n", ConfigCustFile.c_str());
   fprintf(f, "ConfigLockFile = %s\n", ConfigLockFile.c_str());
   fprintf(f, "AlarmBatchStartTime = %s\n", TimeToString(AlarmBatchStartTime).c_str());
   fprintf(f, "NextBatchAlarmTime = %s\n", TimeToString(NextBatchAlarmTime).c_str());
   fprintf(f, "NextBatchEndAlarmTime = %s\n", TimeToString(NextBatchEndAlarmTime).c_str());
   fprintf(f, "BatchAlarmedFlag = %d\n", BatchAlarmedFlag);
   fprintf(f, "BatchEndAlarmedFlag = %d\n", BatchEndAlarmedFlag);
   fprintf(f, "SockDb = %d\n", SockDb);
   fprintf(f, "VarpoolFile = %s\n", VarpoolFile.c_str());
   fprintf(f, "VarpoolLockFile = %s\n", VarpoolLockFile.c_str());
   fprintf(f, "CtldatFile = %s\n", CtldatFile.c_str());
   fprintf(f, "CtldatLockFile = %s\n", CtldatLockFile.c_str());
   fprintf(f, "LastBatchTime = %s\n", LastBatchTime);
   fprintf(f, "RebuildSummary = %d\n", RebuildSummary);
   fprintf(f, "JobEventFd = %d\n", JobEventFd);
   fprintf(f, "BatchDate = %s\n", BatchDate.c_str());
   fprintf(f, "NextBatchDate = %s\n", NextBatchDate.c_str());
   fprintf(f, "EndBatchDate = %s\n", EndBatchDate.c_str());
   fprintf(f, "NextScheduleDate = %s\n", NextScheduleDate.c_str());
   fprintf(f, "BatchTimeTick = %d\n", BatchTimeTick);
   fprintf(f, "RunningJobNum = %d\n", RunningJobNum);
   fprintf(f, "TotalJobNum = %d\n", TotalJobNum);
   fprintf(f, "CompletedJobNum = %d\n", CompletedJobNum);
   fprintf(f, "AnnounceCompleteFlag = %d\n", AnnounceCompleteFlag);
   fprintf(f, "SlaveSignonTime = %s\n", TimeToString(SlaveSignonTime).c_str());
   fprintf(f, "MasterCheckTime = %s\n", TimeToString(MasterCheckTime).c_str());
   fprintf(f, "FsCheckTime = %s\n", TimeToString(FsCheckTime).c_str());
   fprintf(f, "PreparedJobVec.size() = %d\n", PreparedJobVec.size());
   fprintf(f, "ParaMonFile = %p\n", ParaMonFile);
   fprintf(f, "CurrentSection = %s\n", CurrentSection.c_str());
   fprintf(f, "ChangeJobConditionFlag = %d\n", ChangeJobConditionFlag);
   fprintf(f, "BatchRestartFlag = %d\n", BatchRestartFlag);
   fprintf(f, "LastHotbackSuccTime = %s\n", TimeToString(LastHotbackSuccTime).c_str());
   fprintf(f, "LastHotbackFailTime = %s\n", TimeToString(LastHotbackFailTime).c_str());
   fprintf(f, "HotbackObjVec.size() = %d\n", HotbackObjVec.size());
   fprintf(f, "HotbackJobEventQty = %d\n", HotbackJobEventQty);
   fprintf(f, "GatewayIdx = %d\n", GatewayIdx);
   fprintf(f, "TouchGatewayTime = %s\n", TimeToString(TouchGatewayTime).c_str());
   fprintf(f, "AuthVersion = %d\n", AuthVersion);
   fprintf(f, "HasAlarmFunc = %d\n", HasAlarmFunc);
   fprintf(f, "AlarmIdx = %d\n", AlarmIdx);
   fprintf(f, "AlarmVec.size() = %d\n", AlarmVec.size());
   fprintf(f, "HasOutlineFunc = %d\n", HasOutlineFunc);
   fprintf(f, "OutlineIdx = %d\n", OutlineIdx);
   fprintf(f, "OutlineVec.size() = %d\n", OutlineVec.size());
   fprintf(f, "OutlineMap.size() = %d\n", OutlineMap.size());
   fprintf(f, "\n");
   fprintf(f, "AutoSkip = %d\n", AutoSkip);
   fprintf(f, "AcrossDayEnd = %d\n", AcrossDayEnd);
   fprintf(f, "AutoRerun = %d\n", AutoRerun);
   fprintf(f, "AutoRerunTime = %d\n", AutoRerunTime);
   fprintf(f, "FadingPeriod = %s\n", FadingPeriod.c_str());
   fprintf(f, "PartitionDitherCount = %d\n", PartitionDitherCount);
   fprintf(f, "PartitionDitherStep = %d\n", PartitionDitherStep);
   fprintf(f, "Banner = %s\n", Banner.c_str());
   fprintf(f, "BatchTrigger = %s\n", BatchTrigger.c_str());
   fprintf(f, "JobTrigger = %s\n", JobTrigger.c_str());
   fprintf(f, "MainGroupName = %s\n", MainGroupName.c_str());
   fprintf(f, "\n");
   fprintf(f, "SectionSet\n");
   fprintf(f, "{\n");

   for ( set<HString>::iterator Iter = SectionSet.begin(); Iter != SectionSet.end(); Iter++ )
   {
      fprintf(f, "   %s\n", Iter->c_str());
   }

   fprintf(f, "}\n");

   fprintf(f, "\n");
   fprintf(f, "AppendLogToSummary = %d\n", AppendLogToSummary);
   fprintf(f, "EnableAutoSkip = %d\n", EnableAutoSkip);
   fprintf(f, "LogLocation = %d\n", LogLocation);
   fprintf(f, "ParaJobNum = %d\n", ParaJobNum);
   fprintf(f, "NumOfRegion = %d\n", NumOfRegion);
   fprintf(f, "CodeSet = %d\n", CodeSet);
   fprintf(f, "SystemName = %s\n", SystemName.c_str());
   fprintf(f, "AlarmSystemName = %s\n", AlarmSystemName.c_str());
   fprintf(f, "SystemDescription = %s\n", SystemDescription.c_str());
   fprintf(f, "DedicatedFlag = %d\n", DedicatedFlag);
   fprintf(f, "LocalPort = %d\n", LocalPort);
   fprintf(f, "SyslogDir = %s\n", SyslogDir.c_str());
   fprintf(f, "ClearEventFlag = %d\n", ClearEventFlag);
   fprintf(f, "ClearPartFlag = %d\n", ClearPartFlag);
   fprintf(f, "JobNameDelimiter = %s\n", JobNameDelimiter.c_str());
   fprintf(f, "ShrDir = %s\n", ShrDir.c_str());
   fprintf(f, "LogDir = %s\n", LogDir.c_str());
   fprintf(f, "DailyBatch = %s\n", DailyBatch.c_str());
   fprintf(f, "BatchAlarm = %s\n", BatchAlarm.c_str());
   fprintf(f, "DailyBatchName = %s\n", DailyBatchName.c_str());
   fprintf(f, "NextDailyBatchTime = %s\n", TimeToString(NextDailyBatchTime).c_str());
   fprintf(f, "AlarmBatchName = %s\n", AlarmBatchName.c_str());
   fprintf(f, "SlaveSignonCheckTime = %d\n", SlaveSignonCheckTime);
   fprintf(f, "ClusterFlag = %d\n", ClusterFlag);
   fprintf(f, "MyId = %d\n", MyId);
   fprintf(f, "MyRole = %d\n", MyRole);
   fprintf(f, "HotbackEnableFlag = %d\n", HotbackEnableFlag);
   fprintf(f, "HotbackPausedFlag = %d\n", HotbackPausedFlag);
   fprintf(f, "HotbackAddr = %s\n", HotbackAddr.c_str());
   fprintf(f, "HotbackPort = %d\n", HotbackPort);
   fprintf(f, "MasterDownFlag = %d\n", MasterDownFlag);
   fprintf(f, "\n");
   fprintf(f, "NumOfListener = %d\n", NumOfListener);
   fprintf(f, "NumOfDispatcher = %d\n", NumOfDispatcher);
   fprintf(f, "NumOfExecutor = %d\n", NumOfExecutor);
   fprintf(f, "StatusInputQueueSize = %d\n", StatusInputQueueSize);
   fprintf(f, "DispatchQueueSize = %d\n", DispatchQueueSize);
   fprintf(f, "\n");
   fprintf(f, "MasterTimeDiff = %d\n", MasterTimeDiff);
   fprintf(f, "MasterTimeDiffError = %d\n", MasterTimeDiffError);
   fprintf(f, "PauseReason = %d\n", PauseReason);
   fprintf(f, "NextBatchName = %s\n", NextBatchName.c_str());
   fprintf(f, "\n");
   fprintf(f, "BackupMap\n");
   fprintf(f, "{\n");

   for ( map<HString, HBackupInfo_run>::iterator Iter = BackupMap.begin();
         Iter != BackupMap.end(); Iter++ )
   {
      HBackupInfo_run &Backup = Iter->second;
      fprintf(f, "   BACKUP(%s)\n", Backup.Name.c_str());
      fprintf(f, "   {\n");
      fprintf(f, "      EnableFlag = %d\n", Backup.EnableFlag);
      fprintf(f, "      PausedFlag = %d\n", Backup.PausedFlag);
      fprintf(f, "      LogFlag = %d\n", Backup.LogFlag);
      fprintf(f, "      Idx = %d\n", Backup.Idx);
      fprintf(f, "      LastSuccTime = %s\n", TimeToString(Backup.LastSuccTime).c_str());
      fprintf(f, "      LastFailTime = %s\n", TimeToString(Backup.LastFailTime).c_str());
      fprintf(f, "      Addr = %s\n", Backup.Addr.c_str());
      fprintf(f, "      Port = %d\n", Backup.Port);
      fprintf(f, "      JobEventQty = %d\n", Backup.JobEventQty);
      fprintf(f, "      AddrVec\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Backup.AddrVec.size(); i++ )
      {
         fprintf(f, "         %s\n", Backup.AddrVec[i].c_str());
      }
      fprintf(f, "      }\n");
      fprintf(f, "      BackupObjVec\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Backup.BackupObjVec.size(); i++ )
      {
         HBackupObj &Obj = Backup.BackupObjVec[i];
         fprintf(f, "         {\n");
         fprintf(f, "            Type = %d\n", Obj.Type);
         fprintf(f, "            Name = %s\n", Obj.Name);
         fprintf(f, "            Offset = %ld\n", Obj.Offset);
         fprintf(f, "         }\n");
      }
      fprintf(f, "      }\n");
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "GatewayAddrVec\n");
   fprintf(f, "{\n");

   for ( int i = 0; i < GatewayAddrVec.size(); i++ )
   {
      HString &Addr = GatewayAddrVec[i];
      fprintf(f, "   %s\n", Addr.c_str());
   }

   fprintf(f, "}\n\n");

   fprintf(f, "DownloadAuth = %d\n", DownloadAuth);

   fprintf(f, "ScheduleDateSet\n");
   fprintf(f, "{\n");

   for ( set<HString>::iterator Iter = ScheduleDateSet.begin(); Iter != ScheduleDateSet.end(); Iter++ )
   {
      fprintf(f, "   %s\n", Iter->c_str());
   }

   fprintf(f, "}\n\n");

   fprintf(f, "WhiteListMap\n");
   fprintf(f, "{\n");

   for ( map<HString, set<HString> >::iterator Iter = WhiteListMap.begin(); Iter != WhiteListMap.end(); Iter++ )
   {
      fprintf(f, "   Name(%s)\n", Iter->first.c_str());
      fprintf(f, "   {\n");
      for ( set<HString>::iterator SetIter = Iter->second.begin(); SetIter != Iter->second.end(); SetIter++ )
      {
         fprintf(f, "      %s\n", (*SetIter).c_str());
      }
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   {
      static char Str[] =
      {
         0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68,
         0x74, 0x28, 0x43, 0x29, 0x20, 0x32, 0x30, 0x31,
         0x33, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x38, 0x2c,
         0x20, 0x53, 0x68, 0x65, 0x6e, 0x5a, 0x68, 0x65,
         0x6e, 0x20, 0x53, 0x69, 0x6d, 0x6a, 0x69, 0x65,
         0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
         0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x65, 0x63,
         0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x20,
         0x4c, 0x74, 0x64, 0x2e, 0x00,
      };
      fprintf(f, "%s\n", Str);
   }

   fprintf(f, "\n");
   fprintf(f, "Batch\n");
   fprintf(f, "{\n");
   fprintf(f, "   Status = %s\n", BatchStatusToString(Batch.Status));
   fprintf(f, "   StartTime = %s\n", Batch.StartTime);
   fprintf(f, "   EndTime = %s\n", Batch.EndTime);
   fprintf(f, "   Name = %s\n", Batch.Name);
   fprintf(f, "}\n\n");

   fprintf(f, "Ctldat\n");
   fprintf(f, "{\n");
   fprintf(f, "   SeqDate = %s\n", Ctldat.SeqDate);
   fprintf(f, "   Seq = %d\n", Ctldat.Seq);
   fprintf(f, "   ConfigVersion = %d\n", Ctldat.ConfigVersion);
   fprintf(f, "   VarpoolVersion = %d\n", Ctldat.VarpoolVersion);
   fprintf(f, "}\n\n");

   fprintf(f, "ClusterNodeVec\n");
   fprintf(f, "{\n");

   for ( int i = 0; i < ClusterNodeVec.size(); i++ )
   {
      HClusterNodeInfo &Node = ClusterNodeVec[i];
      fprintf(f, "   Node(%d)\n", Node.Id);
      fprintf(f, "   {\n");
      fprintf(f, "      Addr = %s\n", Node.Addr.c_str());
      fprintf(f, "      Port = %d\n", Node.Port);
      fprintf(f, "      GroupIdx = %d\n", Node.GroupIdx);
      fprintf(f, "      SignonFlag = %d\n", Node.SignonFlag);
      fprintf(f, "      LastTouchTime = %s\n", TimeToString(Node.LastTouchTime).c_str());
      fprintf(f, "      RunningJobNum = %d\n", Node.RunningJobNum);
      fprintf(f, "      RunningWaitJobNum = %d\n", Node.RunningWaitJobNum);
      fprintf(f, "      ParaJobNum = %d\n", Node.ParaJobNum);
      fprintf(f, "      RegionJobNum\n");
      fprintf(f, "      {\n");
      for ( int j = 0; j < Node.RegionJobNumVec.size(); j++ )
      {
         fprintf(f, "         %d\n", Node.RegionJobNumVec[j]);
      }
      fprintf(f, "      }\n");
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "RunningGroupMap\n");
   fprintf(f, "{\n");

   for ( map<HString, int>::iterator Iter = RunningGroupMap.begin();
         Iter != RunningGroupMap.end(); Iter++ )
   {
      fprintf(f, "   %s, %d\n", Iter->first.c_str(), Iter->second);
   }

   fprintf(f, "}\n\n");

   fprintf(f, "LoginMap\n");
   fprintf(f, "{\n");

   for ( map<HString, HLoginInfo>::iterator Iter = LoginMap.begin();
         Iter != LoginMap.end(); Iter++ )
   {
      HLoginInfo &Login = Iter->second;
      fprintf(f, "   User(%s)\n", Login.Id.c_str());
      fprintf(f, "   {\n");
      for ( set<HString>::iterator Iter = Login.AuthSet.begin();
            Iter != Login.AuthSet.end(); Iter++ )
      {
         fprintf(f, "      %s\n", (*Iter).c_str());
      }
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "BusinessTrafficControlMap\n");
   fprintf(f, "{\n");

   for ( map<HString, HBusinessTrafficControl>::iterator Iter = BusinessTrafficControlMap.begin();
         Iter != BusinessTrafficControlMap.end(); Iter++ )
   {
      HBusinessTrafficControl &Control = Iter->second;
      fprintf(f, "   Type(%s)\n", Control.Type.c_str());
      fprintf(f, "   {\n");
      fprintf(f, "      ParaJobNum = %d\n", Control.ParaJobNum);
      fprintf(f, "      RunningJobNum = %d\n", Control.RunningJobNum);
      fprintf(f, "      PreparedJobNum = %d\n", Control.PreparedJobNum);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "FsCheckVec\n");
   fprintf(f, "{\n");

   for ( int i = 0; i < FsCheckVec.size(); i++ )
   {
      HFsCheckInfo &FsCheck = FsCheckVec[i];
      fprintf(f, "   {\n");
      fprintf(f, "      Path = %s\n", FsCheck.Path.c_str());
      fprintf(f, "      Size = %d\n", FsCheck.Size);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "Graph\n");
   fprintf(f, "{\n");

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      fprintf(f, "   Job(%s)\n", Job.Name.c_str());
      fprintf(f, "   {\n");
      fprintf(f, "      GroupId = %s\n", Job.GroupId.c_str());
      fprintf(f, "      Type = %s\n", JobTypeToString(Job.Type).c_str());
      fprintf(f, "      BusinessType = %s\n", Job.BusinessType.c_str());
      fprintf(f, "      Command = %s\n", Job.Command.c_str());
      fprintf(f, "      RestoreCommand = %s\n", Job.RestoreCommand.c_str());
      fprintf(f, "      AdditionalLog = %s\n", Job.AdditionalLog.c_str());
      fprintf(f, "      AlarmTimeType = %d\n", Job.AlarmTimeType);
      fprintf(f, "      AlarmTime = %s\n", Job.AlarmTime.c_str());
      fprintf(f, "      ExternJob = %s\n", Job.ExternJob.c_str());
      fprintf(f, "      ExternJobList\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Job.ExternJobList.size(); i++ )
      {
         fprintf(f, "         %s\n", Job.ExternJobList[i].c_str());
      }
      fprintf(f, "      }\n");
      fprintf(f, "      Group = %s\n", Job.Group.c_str());
      fprintf(f, "      ParaJobNum = %d\n", Job.ParaJobNum);
      fprintf(f, "      WaitForOperator = %d\n", Job.WaitForOperator);
      fprintf(f, "      Anchor = %d\n", Job.Anchor);
      fprintf(f, "      Tag = %s\n", Job.Tag.c_str());
      fprintf(f, "      CondNot = %d\n", Job.CondNot);
      fprintf(f, "      ConditionFile = %s\n", Job.ConditionFile.c_str());
      fprintf(f, "      SpecialDate = %s\n", Job.SpecialDate.c_str());
      fprintf(f, "      JobTrigger = %s\n", Job.JobTrigger.c_str());
      fprintf(f, "      PartitionFile = %s\n", Job.PartitionFile.c_str());
      fprintf(f, "      Exclusive = %d\n", Job.Exclusive);
      fprintf(f, "      Priority = %d\n", Job.Priority);
      fprintf(f, "      AutoRerun = %d\n", Job.AutoRerun);
      fprintf(f, "      AutoRerunTime = %d\n", Job.AutoRerunTime);
      fprintf(f, "      RunningGroupIdx = %d\n", Job.RunningGroupIdx);
      fprintf(f, "      LoopStartTimeStr = %s\n", Job.LoopStartTimeStr.c_str());
      fprintf(f, "      LoopEndTimeStr = %s\n", Job.LoopEndTimeStr.c_str());
      fprintf(f, "      LoopStartTime = %s\n", TimeToString(Job.LoopStartTime).c_str());
      fprintf(f, "      LoopEndTime = %s\n", TimeToString(Job.LoopEndTime).c_str());
      fprintf(f, "      LoopInterval = %d\n", Job.LoopInterval);
      fprintf(f, "      LoopCount = %d\n", Job.LoopCount);
      fprintf(f, "      LoopConditionVariable = %s\n", Job.LoopConditionVariable.c_str());
      fprintf(f, "      PartitionStartTimeTick = %d\n", Job.PartitionStartTimeTick);
      fprintf(f, "      InScheduleList = %d\n", Job.InScheduleList);
      fprintf(f, "      Status = %s\n", JobStatusToString(Job.Status));
      fprintf(f, "      RestoreStatus = %d\n", Job.RestoreStatus);
      fprintf(f, "      ServerId = %d\n", Job.ServerId);
      fprintf(f, "      RegionId = %d\n", Job.RegionId);
      fprintf(f, "      Pid = %d\n", Job.Pid);
      fprintf(f, "      RunningTimes = %d\n", Job.RunningTimes);
      fprintf(f, "      LastUserStopTime = %s\n", TimeToString(Job.LastUserStopTime).c_str());
      fprintf(f, "      LastTriggerPid = %d\n", Job.LastTriggerPid);
      fprintf(f, "      AlarmedFlag = %d\n", Job.AlarmedFlag);
      fprintf(f, "      SuccJobs\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Job.SuccJobs.size(); i++ )
      {
         fprintf(f, "         %s\n", Job.SuccJobs[i].c_str());
      }
      fprintf(f, "      }\n");
      fprintf(f, "      Alias\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Job.Alias.size(); i++ )
      {
         fprintf(f, "         %s\n", Job.Alias[i].c_str());
      }
      fprintf(f, "      }\n");
      fprintf(f, "      InDegree = %d\n", Job.InDegree);
      fprintf(f, "      InDegreeWithinGroup = %d\n", Job.InDegreeWithinGroup);
      fprintf(f, "      PartFlag = %d\n", Job.PartFlag);
      fprintf(f, "      PartVec\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         fprintf(f, "         Part(%s)\n", Part.Id.c_str());
         fprintf(f, "         {\n");
         fprintf(f, "            Idx = %d\n", Part.Idx);
         fprintf(f, "            Status = %s\n", JobStatusToString(Part.Status));
         fprintf(f, "            RestoreStatus = %d\n", Part.RestoreStatus);
         fprintf(f, "            ServerId = %d\n", Part.ServerId);
         fprintf(f, "            RegionId = %d\n", Part.RegionId);
         fprintf(f, "            Pid = %d\n", Part.Pid);
         fprintf(f, "            RunningTimes = %d\n", Part.RunningTimes);
         fprintf(f, "            LastUserStopTime = %s\n", TimeToString(Part.LastUserStopTime).c_str());
         fprintf(f, "            LastTriggerPid = %d\n", Part.LastTriggerPid);
         fprintf(f, "            LastStatusChangeTime = %s\n", TimeToString(Part.LastStatusChangeTime).c_str());
         fprintf(f, "            RestartFlag = %d\n", Part.RestartFlag);
         fprintf(f, "            PreparedFlag = %d\n", Part.PreparedFlag);
         fprintf(f, "            BeginningTime = %s\n", TimeToString(Part.BeginningTime).c_str());
         fprintf(f, "            StartTime = %s\n", TimeToString(Part.StartTime).c_str());
         fprintf(f, "            EndTime = %s\n", TimeToString(Part.EndTime).c_str());
         fprintf(f, "            SlaveNotSignon = %d\n", Part.SlaveNotSignon);
         fprintf(f, "         }\n");
      }
      fprintf(f, "      }\n");
      fprintf(f, "      NumOfWaitingPart = %d\n", Job.NumOfWaitingPart);
      fprintf(f, "      NumOfRunningPart = %d\n", Job.NumOfRunningPart);
      fprintf(f, "      NumOfCompletedPart = %d\n", Job.NumOfCompletedPart);
      fprintf(f, "      NumOfFailedPart = %d\n", Job.NumOfFailedPart);
      fprintf(f, "      NumOfUnknownPart = %d\n", Job.NumOfUnknownPart);
      fprintf(f, "      NumOfPreparedPart = %d\n", Job.NumOfPreparedPart);
      fprintf(f, "      LastStatusChangeTime = %s\n", TimeToString(Job.LastStatusChangeTime).c_str());
      fprintf(f, "      IdleTimeSum = %d\n", Job.IdleTimeSum);
      fprintf(f, "      IdleTimeBegin = %s\n", TimeToString(Job.IdleTimeBegin).c_str());
      fprintf(f, "      RestartFlag = %d\n", Job.RestartFlag);
      fprintf(f, "      RestartDate = %s\n", Job.RestartDate.c_str());
      fprintf(f, "      RestartNextDate = %s\n", Job.RestartNextDate.c_str());
      fprintf(f, "      PreparedFlag = %d\n", Job.PreparedFlag);
      fprintf(f, "      GroupTrafficControlFlag = %d\n", Job.GroupTrafficControlFlag);
      fprintf(f, "      UpperGroupVec\n");
      fprintf(f, "      {\n");
      for ( int i = 0; i < Job.UpperGroupVec.size(); i++ )
      {
         fprintf(f, "         %s\n", Job.UpperGroupVec[i].c_str());
      }
      fprintf(f, "      }\n");
      fprintf(f, "      SchedulePartIdx = %d\n", Job.SchedulePartIdx);
      fprintf(f, "      BeginningTime = %s\n", TimeToString(Job.BeginningTime).c_str());
      fprintf(f, "      StartTime = %s\n", TimeToString(Job.StartTime).c_str());
      fprintf(f, "      EndTime = %s\n", TimeToString(Job.EndTime).c_str());
      fprintf(f, "      SlaveNotSignon = %d\n", Job.SlaveNotSignon);
      fprintf(f, "      StopAndSkipFlag = %d\n", Job.StopAndSkipFlag);
      fprintf(f, "      BreakpointFlag = %d\n", Job.BreakpointFlag);
      fprintf(f, "      Step = %d\n", Job.Step);
      fprintf(f, "      Section = %s\n", Job.Section.c_str());
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "GroupMap\n");
   fprintf(f, "{\n");

   for ( map<HString, group_info>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      group_info &Group = Iter->second;
      fprintf(f, "   Group(%s)\n", Group.Name.c_str());
      fprintf(f, "   {\n");
      fprintf(f, "      JobQty = %d\n", Group.JobQty);
      fprintf(f, "      CompletedJobQty = %d\n", Group.CompletedJobQty);
      fprintf(f, "      RunningJobQty = %d\n", Group.RunningJobQty);
      fprintf(f, "      FailedJobQty = %d\n", Group.FailedJobQty);
      fprintf(f, "      FailedExternJobQty = %d\n", Group.FailedExternJobQty);
      fprintf(f, "      UnknownJobQty = %d\n", Group.UnknownJobQty);
      fprintf(f, "      UnknownExternJobQty = %d\n", Group.UnknownExternJobQty);
      fprintf(f, "      Agent = %s\n", Group.Agent.c_str());
      fprintf(f, "      CondNot = %d\n", Group.CondNot);
      fprintf(f, "      ConditionFile = %s\n", Group.ConditionFile.c_str());
      fprintf(f, "      SpecialDate = %s\n", Group.SpecialDate.c_str());
      fprintf(f, "      ConditionDateCache = %d\n", Group.ConditionDateCache);
      fprintf(f, "      ConditionFileCache = %d\n", Group.ConditionFileCache);
      fprintf(f, "      ParaJobNum = %d\n", Group.ParaJobNum);
      fprintf(f, "      RunningJobNum = %d\n", Group.RunningJobNum);
      fprintf(f, "      RunningWaitJobNum = %d\n", Group.RunningWaitJobNum);
      fprintf(f, "      PreparedJobNum = %d\n", Group.PreparedJobNum);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "ScheduleList\n");
   fprintf(f, "{\n");

   for ( list<HString>::iterator Iter = ScheduleList.begin();
         Iter != ScheduleList.end(); Iter++ )
   {
      fprintf(f, "   %s\n", (*Iter).c_str());
   }

   fprintf(f, "}\n\n");

   fprintf(f, "RunningJobMap\n");
   fprintf(f, "{\n");

   for ( map<HString, HRunningJobInfo>::iterator Iter = RunningJobMap.begin();
         Iter != RunningJobMap.end(); Iter++ )
   {
      HRunningJobInfo &Job = Iter->second;
      fprintf(f, "   Job(%s)\n", Job.Name.c_str());
      fprintf(f, "   {\n");
      fprintf(f, "      ServerId = %d\n", Job.ServerId);
      fprintf(f, "      JobStartTime = %s\n", TimeToString(Job.JobStartTime).c_str());
      fprintf(f, "      Pid = %d\n", Job.Pid);
      fprintf(f, "      Status = %s\n", JobStatusToString(Job.Status));
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "JobAlarmMap\n");
   fprintf(f, "{\n");

   for ( map<HString, time_t>::iterator Iter = JobAlarmMap.begin();
         Iter != JobAlarmMap.end(); Iter++ )
   {
      HString JobName = Iter->first;
      time_t Time = Iter->second;
      fprintf(f, "   {\n");
      fprintf(f, "      Job(%s)\n", JobName.c_str());
      fprintf(f, "      Time(%s)\n", TimeToString(Time).c_str());
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   int Header = 0;
   int Qty = 0;

   fprintf(f, "InputQueue\n");
   fprintf(f, "{\n");
   InputQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty; i++ )
   {
      const HInputInfo &Item = InputQueue.GetByIdx(Header + i);
      fprintf(f, "   Item(%d)\n", ( Header + i ) % InputQueue.GetLength());
      fprintf(f, "   {\n");
      fprintf(f, "      Func = %s\n", GetFuncCodeString(Item.Func));
      fprintf(f, "      CodeSet = %s\n", Item.CodeSet.c_str());
      fprintf(f, "      Id = %s\n", Item.Id.c_str());
      fprintf(f, "      ObjName = %s\n", Item.ObjName.c_str());
      fprintf(f, "      IpAddr = %s\n", Item.IpAddr.c_str());
      fprintf(f, "      ArgVec.size() = %d\n", Item.ArgVec.size());
      fprintf(f, "      Fd = %d\n", Item.Fd);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "StatusInputQueue\n");
   fprintf(f, "{\n");
   StatusInputQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty; i++ )
   {
      const HStatusInputInfo &Item = StatusInputQueue.GetByIdx(Header + i);
      fprintf(f, "   Item(%d)\n", ( Header + i ) % InputQueue.GetLength());
      fprintf(f, "   {\n");
      fprintf(f, "      Func = %s\n", GetFuncCodeString(Item.Func));
      fprintf(f, "      FullName = %s\n", Item.FullName);
      fprintf(f, "      JobStartTime = %s\n", TimeToString(Item.JobStartTime).c_str());
      fprintf(f, "      Pid = %d\n", Item.Pid);
      fprintf(f, "      IpAddr = %s\n", Item.IpAddr);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "PostFuncQueue\n");
   fprintf(f, "{\n");
   PostFuncQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty; i++ )
   {
      const HPostFuncInfo &Item = PostFuncQueue.GetByIdx(Header + i);
      fprintf(f, "   Item(%d)\n", ( Header + i ) % PostFuncQueue.GetLength());
      fprintf(f, "   {\n");
      fprintf(f, "      Func = %s\n", GetFuncCodeString(Item.Func));
      fprintf(f, "      FullName = %s\n", Item.FullName.c_str());
      fprintf(f, "      ServerId = %d\n", Item.ServerId);
      fprintf(f, "      JobStartTime = %s\n", TimeToString(Item.JobStartTime).c_str());
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");

   fprintf(f, "DispatchQueue\n");
   fprintf(f, "{\n");
   DispatchQueue.GetCurrForRead(Header, Qty);

   for ( int i = 0; i < Qty; i++ )
   {
      const HDispatchInfo &Item = DispatchQueue.GetByIdx(Header + i);
      fprintf(f, "   Item(%d)\n", ( Header + i ) % DispatchQueue.GetLength());
      fprintf(f, "   {\n");
      fprintf(f, "      Func = %d\n", Item.Input.Func);
      fprintf(f, "      FullName = %s\n", Item.Input.FullName);
      fprintf(f, "      EnvName = %s\n", Item.Input.EnvName);
      fprintf(f, "      CmdStrLen = %d\n", Item.Input.CmdStrLen);
      fprintf(f, "      SrcServerId = %d\n", Item.Input.SrcServerId);
      fprintf(f, "      DesServerId = %d\n", Item.Input.DesServerId);
      fprintf(f, "      JobStartTime = %s\n", TimeToString(Item.Input.JobStartTime).c_str());
      fprintf(f, "      CommStartTime = %s\n", TimeToString(Item.Input.CommStartTime).c_str());
      fprintf(f, "      DitherMs = %d\n", Item.Input.DitherMs);
      fprintf(f, "      RerunFlag = %d\n", Item.Input.RerunFlag);
      fprintf(f, "      RestartFlag = %d\n", Item.Input.RestartFlag);
      fprintf(f, "      Command = %s\n", Item.Command);
      fprintf(f, "   }\n");
   }

   fprintf(f, "}\n\n");
   fclose(f);
}

static void FuncExecuteCommand(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 16 ||
        Input.ArgVec[3].length() > 200 ||
        Input.ArgVec[15].length() > 16384 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString FullName = Input.ArgVec[3];
   int SrcServerId = StrToInt(Input.ArgVec[4].c_str());
   int DesServerId = StrToInt(Input.ArgVec[5].c_str());
   time_t JobStartTime = StrToLong(Input.ArgVec[6].c_str());
   time_t CommStartTime = StrToLong(Input.ArgVec[7].c_str());
   bool RerunFlag = ( Input.ArgVec[8] == "Y" );
   bool RestartFlag = ( Input.ArgVec[9] == "Y" );
   time_t LoopStartTime = StrToLong(Input.ArgVec[10].c_str());
   time_t LoopEndTime = StrToLong(Input.ArgVec[11].c_str());
   int LoopInterval = StrToInt(Input.ArgVec[12].c_str());
   int LoopCount = StrToInt(Input.ArgVec[13].c_str());
   HString JobSeq = Input.ArgVec[14];

   if ( DesServerId != MyId )
   {
      snprintf(ErrMsg, 2048, "Destination is not this node");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( LoopStartTime > 0 )
   {
      LoopStartTime += MasterTimeDiff;
   }

   if ( LoopEndTime > 0 )
   {
      LoopEndTime += MasterTimeDiff;
   }

   GraphMutex.Lock();

   if ( MasterTimeDiffError != -1 )
   {
      time_t CurrTime = time(NULL);
      int DeltaTime = CurrTime + MasterTimeDiff - CommStartTime;
      if ( DeltaTime > 8 )
      {
         GraphMutex.Unlock();
         snprintf(ErrMsg, 2048, "Request is timeout(%d), job = %s, time = %s",
            DeltaTime, FullName.c_str(), TimeToString(CommStartTime).c_str());
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
   }

   HRunningJobInfo RunningJob;

   RunningJob.Name = FullName;
   RunningJob.ServerId = MyId;
   RunningJob.JobStartTime = JobStartTime;
   RunningJob.Pid = 0;
   RunningJobMap[RunningJob.Name] = RunningJob;
   GraphMutex.Unlock();
   ReturnSuccess(Input);

   HExecInput ExecInput;
   HExecOutput ExecOutput;
   char Command[8192 + 1];

   BkAscToHex(Command, Input.ArgVec[15].c_str(), Input.ArgVec[15].length() / 2);
   Command[Input.ArgVec[15].length() / 2] = '\0';
   memset(&ExecInput, 0, sizeof(HExecInput));
   BkStrNCpy(ExecInput.BatchTime, LastBatchTime, sizeof(ExecInput.BatchTime) - 1);
   ExecInput.Func = efJob;
   BkStrNCpy(ExecInput.FullName, FullName.c_str(), sizeof(ExecInput.FullName) - 1);
   ExecInput.SrcServerId = SrcServerId;
   ExecInput.DesServerId = DesServerId;
   ExecInput.JobStartTime = JobStartTime;
   ExecInput.RerunFlag = RerunFlag;
   ExecInput.RestartFlag = RestartFlag;
   ExecInput.LoopStartTime = LoopStartTime;
   ExecInput.LoopEndTime = LoopEndTime;
   ExecInput.LoopInterval = LoopInterval;
   ExecInput.LoopCount = LoopCount;
   BkStrNCpy(ExecInput.JobSeq, JobSeq.c_str(), sizeof(ExecInput.JobSeq) - 1);

   time_t StartTime = time(NULL);
   ExecCmd(Input.ThreadIdx % NumOfExecutor, ExecInput, Command, ExecOutput);
   int DeltaTime = time(NULL) - StartTime;

   if ( DeltaTime > 3 )
   {
      snprintf(ErrMsg, 2048, "ExecCmd(Job = %s), elapsed = %d", ExecInput.FullName, DeltaTime);
      Syslog.Log(__FILE__, __LINE__, llWarn, "OTHER", ErrMsg);
   }

   if ( ExecOutput.Pid == -1 )
   {
      HPostFuncInfo PostFuncInfo;
      PostFuncInfo.Func = fcJobCancel;
      PostFuncInfo.FullName = FullName;
      PostFuncInfo.ServerId = SrcServerId;
      PostFuncInfo.JobStartTime = JobStartTime;
      PostFuncQueue.MasterPushBackNoWait(PostFuncInfo);
      GraphMutex.Lock();
      RunningJobMap.erase(RunningJob.Name);
      GraphMutex.Unlock();
   }
   else
   {
      GraphMutex.Lock();
      RunningJobMap[RunningJob.Name].Pid = ExecOutput.Pid;
      GraphMutex.Unlock();
   }
}

static void FuncFireEvent(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   char EventFileName[500 + 1];
   char SocketName[500 + 1];
   HString EventName = Input.ObjName;

   ToUpper(EventName);
   snprintf(EventFileName, 500, "%s/events/%s.file",
      ShrDir.c_str(), EventName.c_str());
   CreateEmptyFile(EventFileName, ErrMsg);
   snprintf(SocketName, 500, "%s/tmp/%s.sock", SimbatBase, EventName.c_str());
   int SockUD = BsCreateClientSocketUD(SocketName, 0);

   if ( SockUD < 0 && SockUD != BK_ETIMEOUT )
   {
      snprintf(ErrMsg, 2048, "CreateClientSocketUD '%s' fail, errno = %d", SocketName, errno);
      if ( errno == ECONNREFUSED || errno == ENOENT )
      {
         Syslog.Log(__FILE__, __LINE__, llInfo, "ONERR", ErrMsg);
      }
      else
      {
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      }
   }
   else
   {
      if ( SockUD >= 0 )
      {
         close(SockUD);
      }
   }

   ReturnSuccess(Input);
   PushBackupObj(HBackupObj(boEvent, EventName.c_str()));
   DoHotback();
}

static void FuncGetSeq(const HInputInfo &Input)
{
   char JobSeq[18 + 1];

   HJobSeq::GetJobSeq(JobSeq);
   ReturnSuccess(Input, JobSeq);
}

static void FuncGetVar(const HInputInfo &Input)
{
   ReturnSuccess(Input, GetEnv(Input.ObjName).c_str());
}

static void FuncBackupInit(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   int JobEventQty = 0;
   char TmpStr[500 + 1];

   if ( strcmp(LastBatchTime, Input.ObjName.c_str()) != 0 )
   {
      BkStrNCpy(LastBatchTime, Input.ObjName.c_str(), sizeof(LastBatchTime) - 1);
      snprintf(TmpStr, 500, "cd %s/status; rm -f * 2> /dev/null", ShrDir.c_str());
      System.Run(TmpStr);
      if ( ClearPartFlag )
      {
         snprintf(TmpStr, 500, "cd %s/part; rm -f * 2> /dev/null", ShrDir.c_str());
         System.Run(TmpStr);
      }
      snprintf(TmpStr, 500, "cd %s/part_bak; rm -f * 2> /dev/null", ShrDir.c_str());
      System.Run(TmpStr);
      JobEventQty = 0;
      snprintf(TmpStr, 500, "%s/etc/last_batch_time", SimbatBase);
      FILE *f = fopen(TmpStr, "wt");
      if ( f != NULL )
      {
         fprintf(f, "%s\n", LastBatchTime);
         fclose(f);
      }
   }
   else
   {
      snprintf(TmpStr, 500, "%s/status/job_events", ShrDir.c_str());
      JobEventQty = BkFileSize(TmpStr) / sizeof(HJobEvent);
   }

   snprintf(TmpStr, 500, "cd %s/events;rm -f * 2>/dev/null", ShrDir.c_str());
   System.Run(TmpStr);
   snprintf(TmpStr, 500, "%d", JobEventQty);
   ReturnSuccess(Input, TmpStr);
}

static void FuncBackupFile(const HInputInfo &Input)
{
#ifdef SIMBAT_ENTERPRISE
   FuncBackupFile_aux(Input);
#else
   char ErrMsg[2048 + 1];
   snprintf(ErrMsg, 2048, "Only supported in Enterprise Edition");
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
#endif
}

static void FuncBreakpoint(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused )
   {
      snprintf(ErrMsg, 2048, "Batch status is incorrect");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Input.ArgVec.size() < 5 ||
        ( Input.ArgVec[4] != "set" && Input.ArgVec[4] != "unset" ) )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Graph.find(Input.ObjName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[Input.ObjName];

   if ( Input.ArgVec[4] == "set" )
   {
      Job.BreakpointFlag = true;
      Job.Xml->SetProperty("BREAKPOINT", "Y");
   }
   else
   {
      Job.BreakpointFlag = false;
      Job.Xml->SetProperty("BREAKPOINT", "");
   }

   Job.LastStatusChangeTime = time(NULL);
   ReturnSuccess(Input);
}

static void FuncInquiry(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   char FileName1[500 + 1];
   bool CompressFlag = ( Input.ArgVec.size() >= 4 && Input.ArgVec[3] == "Y" );
   HString RefreshTickStr = Input.ArgVec.size() >= 5 ? Input.ArgVec[4] : "";
   bool FullRefresh = ( RefreshTickStr == "" || memcmp(Batch.StartTime, RefreshTickStr.c_str(), 8 + 6) != 0 );
   bool FastFlag = ( Input.ArgVec.size() >= 5 );

   snprintf(FileName1, 500, "inquiry_result1_%d_%d", getpid(), Input.ThreadIdx);

   if ( FullRefresh )
   {
      GraphMutex.Lock();
      SaveOutlineToXml(&SbtXml);
      if ( !SbtXml.SaveToFile(FileName1) )
      {
         GraphMutex.Unlock();
         snprintf(ErrMsg, 2048, "SaveToFile fail : %s", SbtXml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         goto end;
      }
      GraphMutex.Unlock();
   }
   else
   {
      HXmlTree Xml;
      GraphMutex.Lock();
      SaveOutlineToXml(&Xml);
      SaveNewStatusToXml(&Xml, StrToLong(RefreshTickStr.c_str() + 8 + 6));
      Xml.SetProperty("FAST_REFRESH", "Y");
      GraphMutex.Unlock();
      if ( !Xml.SaveToFile(FileName1) )
      {
         snprintf(ErrMsg, 2048, "SaveToFile fail : %s", Xml.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         goto end;
      }
   }

   if ( FastFlag )
   {
      ConvAndCompressFileToSocket(Input.Fd, FileName1,
         CodeSet, StrToCodeSet(Input.CodeSet), CompressFlag);
   }
   else
   {
      if ( CompressFlag )
      {
         CompressFileToSocket(Input.Fd, FileName1,
            CodeSet, StrToCodeSet(Input.CodeSet), false, Input.ThreadIdx);
      }
      else
      {
         ConvFileToSocket(Input.Fd, FileName1,
            CodeSet, StrToCodeSet(Input.CodeSet), Input.ThreadIdx);
      }
   }

end :
   unlink(FileName1);
   close(Input.Fd);
}

static void FuncInquiryCommand(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 6 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString FullName = Input.ArgVec[3];
   time_t JobStartTime = StrToLong(Input.ArgVec[4].c_str());
   int SrcServerId = StrToInt(Input.ArgVec[5].c_str());

   if ( ServerStartTime + MasterTimeDiff > JobStartTime + 3 )
   {
      ReturnFail(Input, "unknown");
      return;
   }

   bool Found = false;
   bool Unknown = false;
   char ResultStr[200 + 1];

   GraphMutex.Lock();

   map<HString, HRunningJobInfo>::iterator Iter = RunningJobMap.find(FullName);

   if ( Iter != RunningJobMap.end() )
   {
      HRunningJobInfo &RunningJob = Iter->second;
      if ( RunningJob.JobStartTime == JobStartTime )
      {
         switch ( RunningJob.Status )
         {
         case jsRunning :
            if ( RunningJob.Pid != 0 )
            {
               if ( kill(RunningJob.Pid, 0) == -1 )
               {
                  Unknown = true;
                  break;
               }
            }
            Found = true;
            snprintf(ResultStr, 200, "R%d", RunningJob.Pid);
            break;
         case jsSuccess :
            Found = true;
            snprintf(ResultStr, 200, "S%d", RunningJob.Pid);
            break;
         case jsFail :
            Found = true;
            snprintf(ResultStr, 200, "F%d", RunningJob.Pid);
            break;
         }
      }
   }

   GraphMutex.Unlock();

   if ( Unknown )
   {
      ReturnFail(Input, "unknown");
      return;
   }

   if ( Found )
   {
      ReturnSuccess(Input, ResultStr);
   }
   else
   {
      ReturnFail(Input, "not_found");
   }
}

static void FuncKillProcess(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 6 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString FullName = Input.ArgVec[3];
   time_t JobStartTime = StrToLong(Input.ArgVec[4].c_str());
   int SrcServerId = StrToInt(Input.ArgVec[5].c_str());

   GraphMutex.Lock();
   map<HString, HRunningJobInfo>::iterator Iter = RunningJobMap.find(FullName);

   if ( Iter != RunningJobMap.end() )
   {
      HRunningJobInfo RunningJob = Iter->second;
      GraphMutex.Unlock();
      if ( RunningJob.JobStartTime == JobStartTime )
      {
         if ( kill(-RunningJob.Pid, SIGINT) == 0 )
         {
            ReturnSuccess(Input);
            return;
         }
      }
   }
   else
   {
      GraphMutex.Unlock();
   }

   ReturnFail(Input, "unknown");
}

static void FuncLogin(const HInputInfo &Input)
{
   char r;
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() > 4 )
   {
      r = CheckPassword(Input.Id, Input.ObjName, Input.ArgVec[4]);
   }
   else
   {
      r = CheckPassword(Input.Id, Input.ObjName, "");
   }

   if ( r == '0' )
   {
      ReturnSuccess(Input);
   }
   else
   {
      snprintf(ErrMsg, 2048, "CheckPassword(%s, %s) fail, r = %c",
         Input.Id.c_str(), Input.ArgVec.size() > 4 ? Input.ArgVec[4].c_str() : "", r);
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input);
   }
}

static void FuncMasterAscend(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( MyRole != nrMaster && MyRole != nrGateway )
   {
      ReturnSuccess(Input);
      MasterTimeDiff = 0;
      MasterTimeDiffError = -1;
      return;
   }

   snprintf(ErrMsg, 2048, "Another master want to ascend, reject it");
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
}

static void FuncNextBatch(const HInputInfo &Input)
{
   HString BatName = Input.ObjName;
   HString ErrStr;
   char ErrMsg[2048 + 1];

   ToLower(BatName);

   if ( Batch.Status == bsNotStarted ||
        Batch.Status == bsStopped )
   {
      if ( StartBatch(BatName.c_str(), ErrStr) )
      {
         ReturnSuccess(Input);
      }
      else
      {
         ReturnFail(Input, ErrStr.c_str());
      }
   }
   else
   {
      NextBatchName = BatName;
      ReturnSuccess(Input);
   }
}

static void FuncOutline(const HInputInfo &Input)
{
   FILE *f = NULL;
   char ErrMsg[2048 + 1];
   char FileName1[500 + 1];
   HXmlTree Xml;
   bool CompressFlag = ( Input.ArgVec.size() >= 4 && Input.ArgVec[3] == "Y" );

   GraphMutex.Lock();
   SaveOutlineToXml(&Xml);
   GraphMutex.Unlock();
   snprintf(FileName1, 500, "inquiry_result1_%d_%d", getpid(), Input.ThreadIdx);

   if ( !Xml.SaveToFile(FileName1) )
   {
      snprintf(ErrMsg, 2048, "SaveToFile fail : %s", Xml.GetErrorString().c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto end;
   }

   if ( CompressFlag )
   {
      CompressFileToSocket(Input.Fd, FileName1,
         CodeSet, StrToCodeSet(Input.CodeSet), false, Input.ThreadIdx);
   }
   else
   {
      ConvFileToSocket(Input.Fd, FileName1,
         CodeSet, StrToCodeSet(Input.CodeSet), Input.ThreadIdx);
   }

end :
   unlink(FileName1);
   close(Input.Fd);
}

static void FuncPauseBatch(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status == bsStarted )
   {
      if ( Batch.Status == bsStarted )
      {
         if ( Input.ArgVec.size() == 4 )
         {
            PauseReason = prFsCheck;
         }
         else
         {
            PauseReason = prUser;
         }
         Batch.Status = bsPaused;
         DoBatchTrigger("PAUSE");
         ReturnSuccess(Input);
      }
      return;
   }

   snprintf(ErrMsg, 2048, "Batch is not started yet");
   ReturnFail(Input, ErrMsg);
}

static void FuncPushAlarm(const HInputInfo &Input)
{
#ifdef SIMBAT_ENTERPRISE
   FuncPushAlarm_aux(Input);
#else
   char ErrMsg[2048 + 1];
   snprintf(ErrMsg, 2048, "Only supported in Enterprise Edition");
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
#endif
}

static void FuncPushOutline(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 4 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( !CheckWhiteList(Input) )
   {
      return;
   }

   HString InfoStr;

   for ( int i = 3; i < Input.ArgVec.size(); i++ )
   {
      if ( i > 3 )
      {
         InfoStr = InfoStr + "|";
      }
      InfoStr = InfoStr + Input.ArgVec[i];
   }
   OutlineMutex.Lock();
   OutlineVec.push_back(InfoStr);
   OutlineMutex.Unlock();
   ReturnSuccess(Input);
}

static void FuncSetVar(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   int MasterId = 0;
   HClusterNodeInfo &Node = ClusterNodeVec[MasterId];
   HString MasterAddr = Node.Addr;
   int MasterPort = Node.Port;

   if ( Input.ArgVec.size() < 5 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( MyRole == nrSlave )
   {
      HString Result;
      char AddrStr[200 + 1];
      char InputStr[500 + 1];
      if ( MasterDownFlag )
      {
         snprintf(AddrStr, 200, "%s:%d", HotbackAddr.c_str(), HotbackPort);
      }
      else
      {
         snprintf(AddrStr, 200, "%s:%d", MasterAddr.c_str(), MasterPort);
      }
      snprintf(InputStr, 500, "g|||%s|%s", Input.ArgVec[3].c_str(), Input.ArgVec[4].c_str());
      if ( CallServer(Syslog, COMM_TIME_OUT, AddrStr, "SET_VAR", "", InputStr, true, Result) )
      {
         ReturnSuccess(Input);
      }
      else
      {
         ReturnFail(Input, Result.c_str());
      }
      return;
   }

   ReturnSuccess(Input);
   SetEnv(Input.ArgVec[3].c_str(), Input.ArgVec[4].c_str());
   BatchDate = GetEnv("SIMBAT_DATE");
   NextBatchDate = GetEnv("SIMBAT_NEXT_DATE");
   NextScheduleDate = GetEnv("SIMBAT_NEXT_SCHEDULE_DATE");
   EndBatchDate = GetEnv("SIMBAT_END_DATE");
}

static void FuncReloadConfig(const HInputInfo &Input)
{
   HXmlTree Cfg;
   HXmlTree CfgCust;
   HXmlTree CfgAuth;
   char ErrMsg[2048 + 1];
   bool OldHotbackEnableFlag = HotbackEnableFlag;
   map<HString, bool> OldBackupEnableMap;

   for ( map<HString, HBackupInfo_run>::iterator Iter = BackupMap.begin();
         Iter != BackupMap.end(); Iter++ )
   {
      OldBackupEnableMap[Iter->first] = Iter->second.EnableFlag;
   }

   ReturnSuccess(Input);

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         snprintf(ErrMsg, 2048, "Load config.xml fail, %s", Cfg.GetErrorString().c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return;
      }
      LoadFromCfg(Cfg);
      if ( FileExist(ConfigCustFile.c_str()) )
      {
         if ( !CfgCust.LoadFromFile(ConfigCustFile.c_str()) )
         {
            snprintf(ErrMsg, 2048, "Load config_cust.xml fail, %s", CfgCust.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return;
         }
         LoadFromCfgCust(CfgCust);
      }
      char FileName[500 + 1];
      snprintf(FileName, 500, "%s/etc/auth.xml", ShrDir.c_str());
      if ( MyRole == nrMaster || MyRole == nrGateway )
      {
         if ( !CfgAuth.LoadFromFile(FileName) )
         {
            snprintf(ErrMsg, 2048, "Load auth.xml fail, %s", CfgAuth.GetErrorString().c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            return;
         }
         LoadFromCfgAuth(CfgAuth);
      }
   }

   if ( MyRole == nrMaster || MyRole == nrSlave )
   {
      HExecInput ExecIn;
      HExecOutput ExecOut;
      memset(&ExecIn, 0, sizeof(ExecIn));
      ExecIn.Func = efReloadConfig;
      for ( int i = 0; i < NumOfExecutor; i++ )
      {
         ExecCmd(i, ExecIn, "", ExecOut);
      }
   }

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      if ( ClusterFlag )
      {
         for ( int i = 0; i < ClusterNodeVec.size(); i++ )
         {
            HClusterNodeInfo &Node = ClusterNodeVec[i];
            if ( i == MyId )
            {
               Node.ParaJobNum = ParaJobNum / 2;
               if ( Node.ParaJobNum == 0 )
               {
                  Node.ParaJobNum = 1;
               }
            }
            else
            {
               if ( Node.ParaJobNum > 0 )
               {
                  Node.ParaJobNum = ParaJobNum;
               }
            }
         }
      }
      else
      {
         HClusterNodeInfo &Node = ClusterNodeVec[0];
         Node.ParaJobNum = ParaJobNum;
      }
      AddConfigVersion();
      PushBackupObj(boAuth);
      PushBackupObj(boConfig);
   }

   {
      bool Changed = false;
      for ( map<HString, HBackupInfo_run>::iterator Iter = BackupMap.begin();
            Iter != BackupMap.end(); Iter++ )
      {
         if ( !Iter->second.EnableFlag )
         {
            continue;
         }
         if ( OldBackupEnableMap.find(Iter->first) == OldBackupEnableMap.end() ||
              !OldBackupEnableMap[Iter->first] )
         {
            Changed = true;
            break;
         }
      }
      if ( !OldHotbackEnableFlag && HotbackEnableFlag )
      {
         Changed = true;
      }
      if ( Changed )
      {
         InitBackupVec();
      }
   }
}

static void FuncRerunJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Input.ObjName == "" )
   {
      job_info Job;
      Job.Type = jtGroup;
      Job.Group = MainGroupName;
      if ( RerunJob(Job, -1) )
      {
         ReturnSuccess(Input);
      }
      else
      {
         snprintf(ErrMsg, 2048, "Nothing to do");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
      }
      return;
   }

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( Job.Type == jtExternJob )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( RerunJob(Job, PartIdx) )
   {
      ReturnSuccess(Input);
   }
   else
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
   }
}

static void FuncStep(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused )
   {
      snprintf(ErrMsg, 2048, "Batch status is incorrect");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Graph.find(Input.ObjName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[Input.ObjName];

   if ( Job.Status != jsRunning )
   {
      snprintf(ErrMsg, 2048, "Job status is incorrect");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   ChangeStatus(Input.ObjName, jsRunning, 88888, 0, 0);
   ReturnSuccess(Input);
}

static void FuncRestartJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted &&
        Batch.Status != bsPaused &&
        Batch.Status != bsStopped )
   {
      snprintf(ErrMsg, 2048, "Batch status is incorrect");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Graph.find(Input.ObjName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString Date = BatchDate;
   HString NextDate = NextBatchDate;

   if ( Input.ArgVec.size() > 4 && Input.ArgVec[4] != "" )
   {
      Date = Input.ArgVec[4];
   }

   if ( Input.ArgVec.size() > 5 && Input.ArgVec[5] != "" )
   {
      NextDate = Input.ArgVec[5];
   }

   if ( !RestartJob(Input.ObjName, Date, NextDate) )
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   ReturnSuccess(Input);
}

static void FuncRestoreJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( Job.Type != jtJob )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   bool Found = false;

   if ( PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( ( Part.Status == jsFail || Part.Status == jsUnknown ) &&
           Part.RestoreStatus == rsNeedRestore )
      {
         RestoreJob(Job, Part.Idx);
         Found = true;
      }
   }
   else
   {
      if ( Job.PartFlag )
      {
         for ( int i = 0; i < Job.PartVec.size(); i++ )
         {
            part_info &Part = Job.PartVec[i];
            if ( ( Part.Status == jsFail || Part.Status == jsUnknown ) &&
                 Part.RestoreStatus == rsNeedRestore )
            {
               RestoreJob(Job, Part.Idx);
               Found = true;
            }
         }
      }
      else
      {
         if ( ( Job.Status == jsFail || Job.Status == jsUnknown ) &&
              Job.RestoreStatus == rsNeedRestore )
         {
            RestoreJob(Job, -1);
            Found = true;
         }
      }
   }

   if ( !Found )
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   ReturnSuccess(Input);
}

static void FuncRestoreJobFail(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused &&
        Batch.Status != bsStopping )
   {
      ReturnFail(Input, "Batch is not started");
      return;
   }

   ReturnSuccess(Input);

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( ( PartFlag && !Job.PartFlag ) ||
        ( !PartFlag && Job.PartFlag ) )
   {
      snprintf(ErrMsg, 2048, "PartFlag not match");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   Job.LastStatusChangeTime = time(NULL);

   if ( PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( Part.RestoreStatus != rsRestoring )
         return;
      Part.RestoreStatus = rsNeedRestore;
      HXmlTree *PartXml = Part.Xml;
      PartXml->SetProperty("RESTORE_STATUS", "NEED_RESTORE");
      Part.LastStatusChangeTime = Job.LastStatusChangeTime;
   }
   else
   {
      if ( Job.RestoreStatus != rsRestoring )
         return;
      Job.RestoreStatus = rsNeedRestore;
      Job.Xml->SetProperty("RESTORE_STATUS", "NEED_RESTORE");
   }
}

static void FuncRestoreJobSuccess(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused &&
        Batch.Status != bsStopping )
   {
      ReturnFail(Input, "Batch is not started");
      return;
   }

   ReturnSuccess(Input);

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( ( PartFlag && !Job.PartFlag ) ||
        ( !PartFlag && Job.PartFlag ) )
   {
      snprintf(ErrMsg, 2048, "PartFlag not match");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   Job.LastStatusChangeTime = time(NULL);

   if ( PartFlag )
   {
      part_info &Part = Job.PartVec[PartIdx];
      if ( Part.RestoreStatus != rsRestoring )
         return;
      Part.RestoreStatus = rsRestored;
      HXmlTree *PartXml = Part.Xml;
      PartXml->SetProperty("RESTORE_STATUS", "RESTORED");
      Part.LastStatusChangeTime = Job.LastStatusChangeTime;
   }
   else
   {
      if ( Job.RestoreStatus != rsRestoring )
         return;
      Job.RestoreStatus = rsRestored;
      Job.Xml->SetProperty("RESTORE_STATUS", "RESTORED");
   }
}

static void FuncResumeBatch(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status == bsPaused )
   {
      Batch.Status = bsStarted;
      if ( PauseReason == prFsCheck )
      {
         int SyncQty;
         if ( !HEventLogger::Sync(SyncQty) )
         {
            Batch.Status = bsPaused;
            ReturnFail(Input, ErrMsg);
            return;
         }
      }
      DoBatchTrigger("CONTINUE");
      ReturnSuccess(Input);
      LastHotbackFailTime = 0;
      return;
   }

   if ( Batch.Status == bsStopped && CompletedJobNum < TotalJobNum )
   {
      ChangeBatchStatus(bsStarted);
      DoBatchTrigger("CONTINUE");
      ReturnSuccess(Input);
      return;
   }

   snprintf(ErrMsg, 2048, "Batch is not paused");
   Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
}

static void FuncSetParaJobNum(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   int NewParaJobNum = StrToInt(Input.ObjName.c_str());

   if ( NewParaJobNum <= 0 || NewParaJobNum > 10000 )
   {
      snprintf(ErrMsg, 2048, "ParaJobNum must between 1 and 10000");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
   }
   else
   {
      ReturnSuccess(Input);
      ParaJobNum = NewParaJobNum;
   }

   if ( ClusterFlag )
   {
      for ( int i = 0; i < ClusterNodeVec.size(); i++ )
      {
         HClusterNodeInfo &Node = ClusterNodeVec[i];
         if ( i == MyId )
         {
            Node.ParaJobNum = ParaJobNum / 2;
            if ( Node.ParaJobNum == 0 )
            {
               Node.ParaJobNum = 1;
            }
         }
         else
         {
            if ( Node.ParaJobNum > 0 )
            {
               Node.ParaJobNum = ParaJobNum;
            }
         }
      }
   }
   else
   {
      HClusterNodeInfo &Node = ClusterNodeVec[0];
      Node.ParaJobNum = ParaJobNum;
   }
}

static void FuncSkipJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Input.ObjName == "" )
   {
      job_info Job;
      Job.Type = jtGroup;
      Job.Group = MainGroupName;
      if ( SkipJob(Job, -1) )
      {
         ReturnSuccess(Input);
      }
      else
      {
         snprintf(ErrMsg, 2048, "Nothing to do");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
      }
      return;
   }

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( Job.Type == jtExternJob )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( SkipJob(Job, PartIdx) )
   {
      ReturnSuccess(Input);
   }
   else
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
   }
}

static void FuncSlaveSignon(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Input.ArgVec.size() < 6 )
   {
      snprintf(ErrMsg, 2048, "Syntax error");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( !ClusterFlag || MyRole != nrMaster )
   {
      snprintf(ErrMsg, 2048, "This node is not master");
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   int SlaveId = StrToInt(Input.ArgVec[3].c_str());
   int SlaveVarpoolVersion = StrToInt(Input.ArgVec[4].c_str());
   time_t SlaveStartTime = StrToLong(Input.ArgVec[5].c_str());

   if ( SlaveId == MyId || SlaveId >= ClusterNodeVec.size() )
   {
      snprintf(ErrMsg, 2048, "Invalid slave id(%d)", SlaveId);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   char ResultStr[200 + 1];
   time_t CurrTime = time(NULL);
   HClusterNodeInfo &Node = ClusterNodeVec[SlaveId];

   snprintf(ResultStr, 200, "%d|%ld|%d|%s", Ctldat.VarpoolVersion,
      CurrTime - ( CurrTime - Input.ArriveTime ) / 2, Ctldat.ConfigVersion, LastBatchTime);
   ReturnSuccess(Input, ResultStr);
   Node.SignonFlag = true;
   Node.LastTouchTime = CurrTime;

   if ( SlaveVarpoolVersion == Ctldat.VarpoolVersion )
   {
      Node.ParaJobNum = ParaJobNum;
   }
   else
   {
      Node.ParaJobNum = 0;
   }

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      if ( !Job.PartFlag )
      {
         if ( Job.ServerId != SlaveId )
         {
            continue;
         }
         if ( SlaveStartTime > Job.StartTime + 3  )
         {
            if ( Job.Status == jsRunning )
            {
               ChangeStatus(Job.Name, jsUnknown, 0, 0, 0);
            }
         }
         else
         {
            if ( Job.Status == jsUnknown && Job.SlaveNotSignon )
            {
               ChangeStatus(Job.Name, jsRunning, Job.ServerId, Job.RegionId, Job.Pid);
            }
         }
         continue;
      }
      if ( Job.Status == jsWaiting || Job.Status == jsSuccess )
      {
         continue;
      }
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.ServerId != SlaveId )
         {
            continue;
         }
         if ( SlaveStartTime > Part.StartTime + 3  )
         {
            if ( Part.Status == jsRunning )
            {
               HString FullName = Job.Name + JobNameDelimiter + PartIdxString(i);
               ChangeStatus(FullName, jsUnknown, 0, 0, 0);
            }
         }
         else
         {
            if ( Part.Status == jsUnknown && Part.SlaveNotSignon )
            {
               HString FullName = Job.Name + JobNameDelimiter + PartIdxString(i);
               ChangeStatus(FullName, jsRunning, Part.ServerId, Part.RegionId, Part.Pid);
            }
         }
         continue;
      }
   }
}

static void FuncStartBatch(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   HString BatName = Input.ObjName;
   HString ErrStr;
   const char *StartDate = NULL;
   const char *EndDate = NULL;
   const char *NextDate = NULL;

   ToLower(BatName);

   if ( Input.ArgVec.size() > 4 && Input.ArgVec[4] != "" )
   {
      StartDate = Input.ArgVec[4].c_str();
      if ( !CheckDate(StartDate) )
      {
         snprintf(ErrMsg, 2048, "Invalid date '%s'", StartDate);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
   }

   if ( Input.ArgVec.size() > 5 && Input.ArgVec[5] != "" )
   {
      EndDate = Input.ArgVec[5].c_str();
      if ( !CheckDate(EndDate) )
      {
         snprintf(ErrMsg, 2048, "Invalid date '%s'", EndDate);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
   }

   if ( Input.ArgVec.size() > 6 && Input.ArgVec[6] != "" )
   {
      NextDate = Input.ArgVec[6].c_str();
      if ( !CheckDate(EndDate) )
      {
         snprintf(ErrMsg, 2048, "Invalid date '%s'", NextDate);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
   }

   if ( StartBatch(BatName.c_str(), ErrStr, false, StartDate, NextDate) )
   {
      ReturnSuccess(Input);
      if ( EndDate != NULL )
      {
         EndBatchDate = EndDate;
         SetEnv("SIMBAT_END_DATE", EndDate);
      }
   }
   else
   {
      ReturnFail(Input, ErrStr.c_str());
   }
}

static void FuncStopBatch(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status == bsStarted || Batch.Status == bsPaused )
   {
      if ( RunningJobNum == 0 )
         SetBatchStopStatus();
      else
      {
         map<HString, job_info>::iterator Iter;
         ChangeBatchStatus(bsStopping);
         for ( Iter = Graph.begin(); Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire &&
                 Job.Type != jtTime && Job.Type != jtBatch )
            {
               continue;
            }
            KillJob(Job.Name);
         }
      }
      ReturnSuccess(Input);
      return;
   }

   if ( Batch.Status == bsStopping )
   {
      ReturnSuccess(Input);
      SetBatchStopStatus();
      return;
   }

   snprintf(ErrMsg, 2048, "Batch is not started yet");
   Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
}

static void FuncStopJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( Input.ObjName == "" )
   {
      job_info Job;
      Job.Type = jtGroup;
      Job.Group = MainGroupName;
      if ( StopJob(Job, -1) )
      {
         ReturnSuccess(Input);
      }
      else
      {
         snprintf(ErrMsg, 2048, "Nothing to do");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
      }
      return;
   }

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( Job.Type == jtExternJob )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   if ( StopJob(Job, PartIdx) )
   {
      ReturnSuccess(Input);
   }
   else
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
   }
}

static void FuncStopAndSkipJob(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];

   if ( Batch.Status != bsStarted && Batch.Status != bsPaused )
   {
      snprintf(ErrMsg, 2048, "Batch is not started yet");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);

   if ( Graph.find(JobName) == Graph.end() )
   {
      snprintf(ErrMsg, 2048, "Job not exist");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   job_info &Job = Graph[JobName];

   if ( Job.Type == jtExternJob || Job.Type == jtGroup )
   {
      snprintf(ErrMsg, 2048, "Job type is not correct");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   bool Found = false;
   time_t CurrTime = time(NULL);

   Job.StopAndSkipFlag = true;

   if ( Job.PartFlag )
   {
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.Status == jsFail || Part.Status == jsUnknown )
         {
            SkipJob(Job, Part.Idx);
            Found = true;
         }
         else if ( Part.Status == jsWaiting )
         {
            HString FullName = Job.Name + JobNameDelimiter + PartIdxString(Part.Idx);
            ChangeStatus(FullName, jsRunning, 0, 0, 0);
            ChangeStatus(FullName, jsSuccess, 0, 0, 0);
            DoTrigger(FullName, "START" , "SUCCESS");
            Found = true;
         }
      }
      for ( int i = 0; i < Job.PartVec.size(); i++ )
      {
         part_info &Part = Job.PartVec[i];
         if ( Part.Status != jsRunning )
         {
            continue;
         }
         Part.LastUserStopTime = CurrTime;
         StopJob(Job, Part.Idx);
         Found = true;
      }
   }
   else
   {
      if ( Job.Status == jsRunning )
      {
         Job.LastUserStopTime = CurrTime;
         StopJob(Job, -1);
         Found = true;
      }
   }

   if ( !Found )
   {
      snprintf(ErrMsg, 2048, "Nothing to do");
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
      ReturnFail(Input, ErrMsg);
      return;
   }

   ReturnSuccess(Input);
}

static void FuncTouchGateway(const HInputInfo &Input)
{
#ifdef SIMBAT_ENTERPRISE
   FuncTouchGateway_aux(Input);
#else
   char ErrMsg[2048 + 1];
   snprintf(ErrMsg, 2048, "Only supported in Enterprise Edition");
   Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
   ReturnFail(Input, ErrMsg);
#endif
}

static void FuncUploadSbt(const HInputInfo &Input)
{
   int i;
   int r;
   int TotalLen;
   HString FileName;
   HString TmpFileName;
   FILE *f;
   char ErrMsg[2048 + 1];

   HCodeSet ClientCodeSet = StrToCodeSet(Input.CodeSet);

   char c = CheckPassword(Input.Id, Input.ArgVec[4], "upload");

   if ( c != '0' )
   {
      snprintf(ErrMsg, 2048, "CheckPassword(%s, %s) fail, r = %c", Input.Id.c_str(), "upload", c);
      Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
   }

   if ( c == '0' )
   {
      BsWriteToSocket(Input.Fd, "0", 1, BK_TIME_INFINITE);
   }
   else
   {
      if ( c == '1' )
      {
         BsWriteToSocket(Input.Fd, "3", 1, BK_TIME_INFINITE);
      }
      else if ( c == '2' )
      {
         BsWriteToSocket(Input.Fd, "4", 1, BK_TIME_INFINITE);
      }
      else
      {
         BsWriteToSocket(Input.Fd, "1", 1, BK_TIME_INFINITE);
      }
      close(Input.Fd);
      return;
   }

   char TmpStr[500 + 1];
   HString ResultFileName;

   {
      HString s = Input.ObjName;
      ToLower(s);
      snprintf(TmpStr, 500, "%s/bats/%s", SimbatBase, s.c_str());
      ResultFileName = TmpStr;
   }

   if ( ClientCodeSet == CodeSet )
   {
      FileName = ResultFileName;
   }
   else
   {
      snprintf(TmpStr, 500, "%s/tmp/upload_%d_%d", SimbatBase, getpid(), Input.ThreadIdx);
      FileName = TmpStr;
   }

   f = fopen(FileName.c_str(), "wb");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not create %s, errno = %d", FileName.c_str(), errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto error;
   }

   r = BsReadFromSocket(Input.Fd, TmpStr, 8, BK_TIME_INFINITE);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsReadFromSocket(8) fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      goto error;
   }

   TmpStr[8] = '\0';
   TotalLen = HexStrToInt(TmpStr);

   for ( i = 0; i < TotalLen; )
   {
      int Len;
      char Buf[1024 + 1];
      if ( TotalLen - i >= 1024 )
      {
         Len = 1024;
      }
      else
      {
         Len = TotalLen - i;
      }
      r = BsReadFromSocket(Input.Fd, Buf, Len, BK_TIME_INFINITE);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsReadFromSocket(%d) fail, r = %d, errno = %d", Len, r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         goto error;
      }
      fwrite(Buf, Len, 1, f);
      i += Len;
   }

   fclose(f);
   BsWriteToSocket(Input.Fd, "0", 1, BK_TIME_INFINITE);
   close(Input.Fd);

   if ( ClientCodeSet != CodeSet )
   {
      ConvFile(ClientCodeSet, CodeSet, FileName, ResultFileName);
      unlink(FileName.c_str());
   }

   return;

error :

   BsWriteToSocket(Input.Fd, "1", 1, BK_TIME_INFINITE);
   close(Input.Fd);
}

static void FuncViewAdditionalLog(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   HString LogFileName = "/dev/null";
   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);
   int ServerId = -1;

   if ( MyRole == nrMaster )
   {
      GraphMutex.Lock();
      if ( Graph.find(JobName) == Graph.end() )
      {
         GraphMutex.Unlock();
         snprintf(ErrMsg, 2048, "Job not exist");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
      job_info &Job = Graph[JobName];
      if ( Job.Type != jtJob || Job.AdditionalLog == "" )
      {
         GraphMutex.Unlock();
         snprintf(ErrMsg, 2048, "Job has no additional log");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
      map<HString, HString> EnvMap;
      if ( PartFlag )
      {
         EnvMap["SIMBAT_PART"] = Job.PartVec[PartIdx].Id;
      }
      LogFileName = EnvEvaluate(Job.AdditionalLog, &EnvMap);
      if ( LogLocation == llLocal )
      {
         if ( PartFlag )
         {
            part_info &Part = Job.PartVec[PartIdx];
            ServerId = Part.ServerId;
         }
         else
         {
            ServerId = Job.ServerId;
         }
         if ( ServerId == MyId )
         {
            ServerId = -1;
         }
      }
      GraphMutex.Unlock();
   }
   else
   {
      if ( Input.ArgVec.size() >= 5 )
      {
         LogFileName = Input.ArgVec[4];
      }
   }

   if ( ServerId == -1 )
   {
      if ( FileExist(LogFileName.c_str()) )
      {
         CompressFileToSocket(Input.Fd, LogFileName.c_str(),
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
      }
      else
      {
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
      }
   }
   else // 转发 slave
   {
      HClusterNodeInfo &Node = ClusterNodeVec[ServerId];
      int Sock = BsCreateClientSocket(Node.Addr.c_str(), Node.Port, COMM_TIME_OUT);
      if ( Sock < 0 )
      {
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      char Buf[2048 + 1];
      snprintf(Buf, 2048, "%sE|%s||%s|%s", PVER, Input.CodeSet.c_str(), Input.ObjName.c_str(),
         LogFileName.c_str());
      int r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "WriteToSocketHSL fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      r = BsReadFromSocket(Sock, Buf, 8, COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsReadFromSocket(8) fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      Buf[8] = '\0';
      r = BsWriteToSocket(Input.Fd, Buf, 8, COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsWriteToSocket(8) fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         goto end;
      }
      int TotalLen = HexStrToInt(Buf);
      for ( ; TotalLen > 0; )
      {
         r = BsReadFromSocket(Sock, Buf, 2048, COMM_TIME_OUT);
         if ( r < 0 )
         {
            snprintf(ErrMsg, 2048, "BsReadFromSocket(8) fail, r = %d, errno = %d", r, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            close(Sock);
            goto end;
         }
         if ( r == 0 )
         {
            break;
         }
         int Len = r;
         r = BsWriteToSocket(Input.Fd, Buf, Len, COMM_TIME_OUT);
         if ( r < 0 )
         {
            snprintf(ErrMsg, 2048, "BsWriteToSocket(%d) fail, r = %d, errno = %d", Len, r, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            close(Sock);
            goto end;
         }
         TotalLen -= Len;
      }
   }

end :
   close(Input.Fd);
}

static void FuncViewLog(const HInputInfo &Input)
{
   char ErrMsg[2048 + 1];
   HString JobName;
   int PartIdx;
   bool PartFlag = SplitPartId(Input.ObjName, JobName, PartIdx);
   int ServerId = -1;
   bool RestoreFlag = Input.ArgVec.size() > 4 && Input.ArgVec[4] == "r";

   if ( MyRole == nrMaster )
   {
      GraphMutex.Lock();
      if ( Graph.find(JobName) == Graph.end() )
      {
         GraphMutex.Unlock();
         snprintf(ErrMsg, 2048, "Job not exist");
         Syslog.Log(__FILE__, __LINE__, llWarn, "ONERR", ErrMsg);
         ReturnFail(Input, ErrMsg);
         return;
      }
      if ( LogLocation == llLocal )
      {
         job_info &Job = Graph[JobName];
         if ( PartFlag )
         {
            part_info &Part = Job.PartVec[PartIdx];
            ServerId = Part.ServerId;
         }
         else
         {
            ServerId = Job.ServerId;
         }
         if ( ServerId == MyId )
         {
            ServerId = -1;
         }
         else if ( !ClusterNodeVec[ServerId].SignonFlag )
         {
            ServerId++;
            if ( ServerId >= ClusterNodeVec.size() )
            {
               ServerId = -1;
            }
         }
      }
      GraphMutex.Unlock();
   }

   if ( ServerId == -1 || RestoreFlag )
   {
      char FileName1[500 + 1];
      snprintf(FileName1, 500, "%s/log_%s/%s%s", LogDir.c_str(), LastBatchTime,
         Input.ObjName.c_str(), RestoreFlag ? "_restore" : "");
      if ( !FileExist(FileName1) )
      {
         BkStrNCpy(FileName1, "/dev/null", sizeof(FileName1) - 1);
      }
      CompressFileToSocket(Input.Fd, FileName1,
         CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
   }
   else // 转发 slave
   {
      HClusterNodeInfo &Node = ClusterNodeVec[ServerId];
      int Sock = BsCreateClientSocket(Node.Addr.c_str(), Node.Port, COMM_TIME_OUT);
      if ( Sock < 0 )
      {
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      char Buf[2048 + 1];
      {
         char TmpStr[500 + 1];
         BkStrNCpy(TmpStr, Input.ObjName.c_str(), sizeof(TmpStr) - 1);
         ConvString(CodeSet, StrToCodeSet(Input.CodeSet), TmpStr);
         snprintf(Buf, 2048, "%s8|%s||%s", PVER, Input.CodeSet.c_str(), TmpStr);
      }
      int r = WriteToSocketHSL(Sock, Buf, strlen(Buf), COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "WriteToSocketHSL fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      r = BsReadFromSocket(Sock, Buf, 8, COMM_TIME_OUT);
      if ( r < 8 )
      {
         snprintf(ErrMsg, 2048, "BsReadFromSocket(8) fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         CompressFileToSocket(Input.Fd, "/dev/null",
            CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
         goto end;
      }
      Buf[8] = '\0';
      r = BsWriteToSocket(Input.Fd, Buf, 8, COMM_TIME_OUT);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "BsWriteToSocket(8) fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         goto end;
      }
      int TotalLen = HexStrToInt(Buf);
      for ( ; TotalLen > 0; )
      {
         r = BsReadFromSocket(Sock, Buf, 2048, COMM_TIME_OUT);
         if ( r < 0 )
         {
            snprintf(ErrMsg, 2048, "BsReadFromSocket(8) fail, r = %d, errno = %d", r, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            close(Sock);
            goto end;
         }
         if ( r == 0 )
         {
            break;
         }
         int Len = r;
         r = BsWriteToSocket(Input.Fd, Buf, Len, COMM_TIME_OUT);
         if ( r < 0 )
         {
            snprintf(ErrMsg, 2048, "BsWriteToSocket(%d) fail, r = %d, errno = %d", Len, r, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            close(Sock);
            goto end;
         }
         TotalLen -= Len;
      }
   }

end :
   close(Input.Fd);
}

static void FuncViewSummary(const HInputInfo &Input)
{
   vector<HString> Text;
   char FileName1[500 + 1];

   snprintf(FileName1, 500, "%s/log_%s/summary.txt", LogDir.c_str(), LastBatchTime);
   CompressFileToSocket(Input.Fd, FileName1,
      CodeSet, StrToCodeSet(Input.CodeSet), true, Input.ThreadIdx);
   close(Input.Fd);
}

static int DbConnect(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "connect|%s", System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int DbCountStatus(const char *System, int *Result, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "count|%s", System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   *Result = StrToInt(ResultStr.c_str());
   return 0;
}

static int DbClearStatus(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "clear|%s", System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int DbWriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "write|%s|%d|%s|%s", System, Idx, RecType, Record);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int DbReadStatus(const char *System, int Idx, char *RecType,
   char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "read|%s|%d", System, Idx);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   vector<HString> StrVec;

   SplitString(ResultStr.c_str(), '|', StrVec, true);

   if ( StrVec.size() < 2 )
   {
      snprintf(ErrStr, 200, "Invalid output %s", ResultStr.c_str());
      return 8;
   }

   BkStrNCpy(RecType, StrVec[0].c_str(), 200);
   BkStrNCpy(Record, StrVec[1].c_str(), 2000);
   return 0;
}

static int DbRewriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "rewrite|%s|%d|%s|%s", System, Idx, RecType, Record);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int DbWriteHis(const char *System, const char *BatchSeq, const char *JobName,
   const char *ValueList, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "write_his|%s|%s|%s|%s", System, BatchSeq, JobName, ValueList);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int DbReadStatistic(const char *System, const char *BatchType, const char *JobName,
   int *n, int Elapse[10], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "read_statistic|%s|%s|%s", System, BatchType, JobName);
  
   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   vector<HString> StrVec;

   SplitString(ResultStr.c_str(), '|', StrVec, true);

   if ( StrVec.size() != 11 )
   {
      snprintf(ErrStr, 200, "Result syntax error : %s", ResultStr.c_str());
      return 8;
   }

   *n = StrToInt(StrVec[0].c_str());

   for ( int i = 0; i < 10; i++ )
   {
      Elapse[i] = StrToInt(StrVec[i + 1].c_str());
   }

   return 0;
}

static int DbWriteStatistic(const char *System, const char *BatchType, const char *JobName,
   int n, int Elapse[10], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;

   snprintf(TmpStr, sizeof(TmpStr), "UD:%s/tmp/socket_db", SimbatBase);

   HString AddrStr = TmpStr;

   snprintf(TmpStr, 4096, "write_statistic|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d",
      System, BatchType, JobName, n,
      Elapse[0], Elapse[1], Elapse[2], Elapse[3], Elapse[4],
      Elapse[5], Elapse[6], Elapse[7], Elapse[8], Elapse[9]);
   
   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbConnect(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||connect|%s", CodeSetToStr(CodeSet).c_str(), System);

   bool b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", "", TmpStr, true, ResultStr);

   if ( !b && GatewayAddrVec.size() > 1 )
   {
      GatewayIdx = 1 - GatewayIdx;
      AddrStr = GatewayAddrVec[GatewayIdx];
      b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", "", TmpStr, true, ResultStr);
   }

   if ( !b )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbCountStatus(const char *System, int *Result, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||count|%s", CodeSetToStr(CodeSet).c_str(), System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   *Result = StrToInt(ResultStr.c_str());
   return 0;
}

static int GatewayDbClearStatus(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||clear|%s", CodeSetToStr(CodeSet).c_str(), System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbWriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||write|%s|%d|%s|%s", CodeSetToStr(CodeSet).c_str(), System, Idx, RecType, Record);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbReadStatus(const char *System, int Idx, char *RecType,
   char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||read|%s|%d", CodeSetToStr(CodeSet).c_str(), System, Idx);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   vector<HString> StrVec;

   SplitString(ResultStr.c_str(), '|', StrVec, true);

   if ( StrVec.size() < 2 )
   {
      snprintf(ErrStr, 200, "Invalid output %s", ResultStr.c_str());
      return 8;
   }

   BkStrNCpy(RecType, StrVec[0].c_str(), 200);
   BkStrNCpy(Record, StrVec[1].c_str(), 2000);
   return 0;
}

static int GatewayDbRewriteStatus(const char *System, int Idx, const char *RecType,
   const char Record[2000 + 1], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||rewrite|%s|%d|%s|%s", CodeSetToStr(CodeSet).c_str(), System, Idx, RecType, Record);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbWriteHis(const char *System, const char *BatchSeq, const char *JobName,
   const char *ValueList, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||write_his|%s|%s|%s|%s", CodeSetToStr(CodeSet).c_str(), System, BatchSeq, JobName, ValueList);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayDbReadStatistic(const char *System, const char *BatchType, const char *JobName,
   int *n, int Elapse[10], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||read_statistic|%s|%s|%s",
      CodeSetToStr(CodeSet).c_str(), System, BatchType, JobName);
  
   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   vector<HString> StrVec;

   SplitString(ResultStr.c_str(), '|', StrVec, true);

   if ( StrVec.size() != 11 )
   {
      snprintf(ErrStr, 200, "Result syntax error : %s", ResultStr.c_str());
      return 8;
   }

   *n = StrToInt(StrVec[0].c_str());

   for ( int i = 0; i < 10; i++ )
   {
      Elapse[i] = StrToInt(StrVec[i + 1].c_str());
   }

   return 0;
}

static int GatewayDbWriteStatistic(const char *System, const char *BatchType, const char *JobName,
   int n, int Elapse[10], char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|%s||write_statistic|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d",
      CodeSetToStr(CodeSet).c_str(), System, BatchType, JobName, n,
      Elapse[0], Elapse[1], Elapse[2], Elapse[3], Elapse[4],
      Elapse[5], Elapse[6], Elapse[7], Elapse[8], Elapse[9]);
   
   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

// === end of function ===

int main(int argc, char *argv[])
{
   BkSignal(SIGSEGV, SignalHandler, 0);
   BkSignal(SIGABRT, SignalHandler, 0);
   BkSignal(SIGILL, SignalHandler, 0);
   SET_ACTION_POINT(main, 0x0001);

   if ( argc != 2 )
   {
      BkPrintError("Line %d : argc != 2", __LINE__);
      return 8;
   }

   SET_ACTION_POINT(main, 0x0002);
   SockSch = StrToInt(argv[1]);

   if ( !Init() )
   {
      return 8;
   }

   SET_ACTION_POINT(main, 0x0003);
   DoIt();
   SET_ACTION_POINT(main, 0x0004);
   return 0;
}
